summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitattributes4
-rw-r--r--.github/workflows/windows_builds.yml11
-rw-r--r--SConstruct44
-rw-r--r--core/SCsub2
-rw-r--r--core/config/project_settings.cpp4
-rw-r--r--core/core_bind.compat.inc45
-rw-r--r--core/core_bind.cpp16
-rw-r--r--core/core_bind.h8
-rw-r--r--core/debugger/remote_debugger_peer.cpp3
-rw-r--r--core/extension/gdextension.cpp10
-rw-r--r--core/extension/gdextension_interface.cpp2
-rw-r--r--core/extension/gdextension_interface.h2
-rw-r--r--core/extension/gdextension_manager.cpp93
-rw-r--r--core/extension/gdextension_manager.h3
-rw-r--r--core/io/file_access.cpp111
-rw-r--r--core/io/file_access.h8
-rw-r--r--core/io/file_access_compressed.cpp39
-rw-r--r--core/io/file_access_compressed.h3
-rw-r--r--core/io/file_access_encrypted.cpp42
-rw-r--r--core/io/file_access_encrypted.h2
-rw-r--r--core/io/file_access_memory.cpp18
-rw-r--r--core/io/file_access_memory.h3
-rw-r--r--core/io/file_access_pack.cpp15
-rw-r--r--core/io/file_access_pack.h4
-rw-r--r--core/io/file_access_zip.cpp8
-rw-r--r--core/io/file_access_zip.h3
-rw-r--r--core/io/image.cpp27
-rw-r--r--core/io/image.h2
-rw-r--r--core/io/json.cpp748
-rw-r--r--core/io/json.h3
-rw-r--r--core/io/logger.cpp6
-rw-r--r--core/io/packed_data_container.h2
-rw-r--r--core/io/resource.cpp99
-rw-r--r--core/io/resource_loader.cpp76
-rw-r--r--core/math/a_star.cpp28
-rw-r--r--core/math/a_star.h4
-rw-r--r--core/math/a_star_grid_2d.cpp36
-rw-r--r--core/math/a_star_grid_2d.h3
-rw-r--r--core/math/math_funcs.h3
-rw-r--r--core/math/random_pcg.cpp5
-rw-r--r--core/object/class_db.cpp26
-rw-r--r--core/object/class_db.h1
-rw-r--r--core/object/object.cpp29
-rw-r--r--core/object/object.h28
-rw-r--r--core/object/script_language.cpp13
-rw-r--r--core/object/script_language.h5
-rw-r--r--core/object/worker_thread_pool.cpp44
-rw-r--r--core/os/os.h1
-rw-r--r--core/os/pool_allocator.cpp588
-rw-r--r--core/os/pool_allocator.h148
-rw-r--r--core/string/string_name.cpp3
-rw-r--r--core/string/translation_server.cpp5
-rw-r--r--core/string/ustring.cpp24
-rw-r--r--core/string/ustring.h8
-rw-r--r--core/variant/binder_common.h4
-rw-r--r--core/variant/callable.cpp2
-rw-r--r--core/variant/variant.cpp9
-rw-r--r--core/variant/variant.h1
-rw-r--r--core/variant/variant_call.cpp2
-rw-r--r--core/variant/variant_internal.h4
-rw-r--r--doc/classes/AStar2D.xml2
-rw-r--r--doc/classes/AStar3D.xml2
-rw-r--r--doc/classes/AStarGrid2D.xml9
-rw-r--r--doc/classes/AnimationMixer.xml14
-rw-r--r--doc/classes/AudioStreamPlayer.xml2
-rw-r--r--doc/classes/AudioStreamPlayer2D.xml2
-rw-r--r--doc/classes/AudioStreamPlayer3D.xml2
-rw-r--r--doc/classes/AudioStreamWAV.xml19
-rw-r--r--doc/classes/CPUParticles3D.xml4
-rw-r--r--doc/classes/CodeEdit.xml3
-rw-r--r--doc/classes/Control.xml7
-rw-r--r--doc/classes/DisplayServer.xml2
-rw-r--r--doc/classes/EditorExportPlatform.xml206
-rw-r--r--doc/classes/EditorExportPlatformExtension.xml282
-rw-r--r--doc/classes/EditorExportPlugin.xml12
-rw-r--r--doc/classes/EditorExportPreset.xml186
-rw-r--r--doc/classes/EditorInterface.xml6
-rw-r--r--doc/classes/EditorPlugin.xml14
-rw-r--r--doc/classes/EditorResourcePreviewGenerator.xml4
-rw-r--r--doc/classes/EditorSettings.xml46
-rw-r--r--doc/classes/EditorSpinSlider.xml8
-rw-r--r--doc/classes/EditorUndoRedoManager.xml15
-rw-r--r--doc/classes/GDExtensionManager.xml14
-rw-r--r--doc/classes/ImporterMesh.xml4
-rw-r--r--doc/classes/JSON.xml20
-rw-r--r--doc/classes/Label.xml2
-rw-r--r--doc/classes/Label3D.xml2
-rw-r--r--doc/classes/PackedScene.xml6
-rw-r--r--doc/classes/ParticleProcessMaterial.xml4
-rw-r--r--doc/classes/PortableCompressedTexture2D.xml2
-rw-r--r--doc/classes/ProjectSettings.xml12
-rw-r--r--doc/classes/PropertyTweener.xml1
-rw-r--r--doc/classes/RenderingServer.xml8
-rw-r--r--doc/classes/ResourceImporterWAV.xml22
-rw-r--r--doc/classes/ResourceLoader.xml8
-rw-r--r--doc/classes/ResourceUID.xml2
-rw-r--r--doc/classes/RichTextLabel.xml9
-rw-r--r--doc/classes/ScriptEditor.xml6
-rw-r--r--doc/classes/Semaphore.xml3
-rw-r--r--doc/classes/Shader.xml4
-rw-r--r--doc/classes/SpinBox.xml94
-rw-r--r--doc/classes/String.xml32
-rw-r--r--doc/classes/StringName.xml32
-rw-r--r--doc/classes/TextEdit.xml5
-rw-r--r--doc/classes/TextMesh.xml2
-rw-r--r--doc/classes/TextParagraph.xml2
-rw-r--r--doc/classes/TileData.xml2
-rw-r--r--doc/classes/TileMapLayer.xml3
-rw-r--r--doc/classes/Tweener.xml2
-rw-r--r--doc/classes/VehicleWheel3D.xml12
-rw-r--r--doc/classes/VisualShaderNodeCubemap.xml2
-rw-r--r--doc/classes/VisualShaderNodeTexture2DArray.xml2
-rw-r--r--drivers/gles3/shaders/scene.glsl2
-rw-r--r--drivers/gles3/shaders/stdlib_inc.glsl18
-rw-r--r--drivers/gles3/storage/light_storage.h2
-rw-r--r--drivers/gles3/storage/material_storage.cpp1
-rw-r--r--drivers/gles3/storage/texture_storage.cpp22
-rw-r--r--drivers/metal/metal_objects.h1
-rw-r--r--drivers/metal/metal_objects.mm8
-rw-r--r--drivers/metal/rendering_device_driver_metal.mm2
-rw-r--r--drivers/unix/file_access_unix.cpp93
-rw-r--r--drivers/unix/file_access_unix.h8
-rw-r--r--drivers/unix/file_access_unix_pipe.cpp25
-rw-r--r--drivers/unix/file_access_unix_pipe.h2
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.cpp11
-rw-r--r--drivers/windows/dir_access_windows.cpp192
-rw-r--r--drivers/windows/file_access_windows.cpp231
-rw-r--r--drivers/windows/file_access_windows.h8
-rw-r--r--drivers/windows/file_access_windows_pipe.cpp24
-rw-r--r--drivers/windows/file_access_windows_pipe.h2
-rw-r--r--editor/action_map_editor.cpp2
-rw-r--r--editor/animation_bezier_editor.cpp6
-rw-r--r--editor/animation_track_editor.cpp244
-rw-r--r--editor/animation_track_editor.h11
-rw-r--r--editor/code_editor.cpp51
-rw-r--r--editor/code_editor.h6
-rw-r--r--editor/debugger/editor_debugger_node.cpp10
-rw-r--r--editor/debugger/editor_debugger_node.h4
-rw-r--r--editor/editor_asset_installer.cpp4
-rw-r--r--editor/editor_audio_buses.cpp8
-rw-r--r--editor/editor_autoload_settings.cpp2
-rw-r--r--editor/editor_build_profile.cpp8
-rw-r--r--editor/editor_build_profile.h2
-rw-r--r--editor/editor_file_system.cpp201
-rw-r--r--editor/editor_file_system.h2
-rw-r--r--editor/editor_help_search.cpp549
-rw-r--r--editor/editor_help_search.h44
-rw-r--r--editor/editor_inspector.cpp2
-rw-r--r--editor/editor_interface.cpp5
-rw-r--r--editor/editor_interface.h2
-rw-r--r--editor/editor_locale_dialog.cpp4
-rw-r--r--editor/editor_log.cpp8
-rw-r--r--editor/editor_log.h2
-rw-r--r--editor/editor_node.cpp38
-rw-r--r--editor/editor_properties_array_dict.cpp8
-rw-r--r--editor/editor_properties_vector.cpp10
-rw-r--r--editor/editor_resource_picker.cpp18
-rw-r--r--editor/editor_resource_picker.h2
-rw-r--r--editor/editor_resource_preview.cpp4
-rw-r--r--editor/editor_run_native.cpp10
-rw-r--r--editor/editor_settings.cpp16
-rw-r--r--editor/editor_settings_dialog.cpp131
-rw-r--r--editor/editor_settings_dialog.h6
-rw-r--r--editor/editor_undo_redo_manager.cpp3
-rw-r--r--editor/editor_undo_redo_manager.h2
-rw-r--r--editor/export/editor_export.cpp18
-rw-r--r--editor/export/editor_export.h2
-rw-r--r--editor/export/editor_export_platform.cpp264
-rw-r--r--editor/export/editor_export_platform.h105
-rw-r--r--editor/export/editor_export_platform_extension.cpp317
-rw-r--r--editor/export/editor_export_platform_extension.h149
-rw-r--r--editor/export/editor_export_platform_pc.cpp6
-rw-r--r--editor/export/editor_export_platform_pc.h8
-rw-r--r--editor/export/editor_export_plugin.cpp11
-rw-r--r--editor/export/editor_export_plugin.h1
-rw-r--r--editor/export/editor_export_preset.cpp42
-rw-r--r--editor/export/editor_export_preset.h7
-rw-r--r--editor/export/export_template_manager.cpp106
-rw-r--r--editor/export/export_template_manager.h2
-rw-r--r--editor/export/project_export.cpp4
-rw-r--r--editor/filesystem_dock.cpp2
-rw-r--r--editor/groups_editor.cpp2
-rw-r--r--editor/gui/editor_bottom_panel.cpp4
-rw-r--r--editor/gui/editor_file_dialog.cpp4
-rw-r--r--editor/gui/editor_run_bar.cpp2
-rw-r--r--editor/gui/editor_spin_slider.cpp6
-rw-r--r--editor/gui/editor_spin_slider.h5
-rw-r--r--editor/gui/editor_zoom_widget.cpp17
-rw-r--r--editor/gui/scene_tree_editor.cpp2
-rw-r--r--editor/history_dock.cpp8
-rw-r--r--editor/icons/GuiSpinboxDown.svg1
-rw-r--r--editor/icons/GuiSpinboxUp.svg1
-rw-r--r--editor/icons/MemberConstructor.svg1
-rw-r--r--editor/icons/MemberOperator.svg1
-rw-r--r--editor/icons/SnapKeys.svg1
-rw-r--r--editor/icons/SnapTimeline.svg1
-rw-r--r--editor/icons/editor_icons_builders.py20
-rw-r--r--editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp2
-rw-r--r--editor/import/3d/post_import_plugin_skeleton_rest_fixer.h2
-rw-r--r--editor/import/3d/resource_importer_scene.cpp109
-rw-r--r--editor/import/3d/resource_importer_scene.h17
-rw-r--r--editor/import/3d/scene_import_settings.cpp16
-rw-r--r--editor/import/3d/scene_import_settings.h2
-rw-r--r--editor/import/audio_stream_import_settings.cpp6
-rw-r--r--editor/import/resource_importer_wav.cpp14
-rw-r--r--editor/input_event_configuration_dialog.cpp4
-rw-r--r--editor/inspector_dock.cpp2
-rw-r--r--editor/plugins/animation_blend_space_1d_editor.cpp2
-rw-r--r--editor/plugins/animation_blend_space_2d_editor.cpp2
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp155
-rw-r--r--editor/plugins/animation_player_editor_plugin.h2
-rw-r--r--editor/plugins/bone_map_editor_plugin.cpp8
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp70
-rw-r--r--editor/plugins/control_editor_plugin.cpp2
-rw-r--r--editor/plugins/curve_editor_plugin.cpp2
-rw-r--r--editor/plugins/editor_plugin.cpp13
-rw-r--r--editor/plugins/editor_plugin.h4
-rw-r--r--editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp12
-rw-r--r--editor/plugins/gizmos/joint_3d_gizmo_plugin.h2
-rw-r--r--editor/plugins/gradient_editor_plugin.cpp2
-rw-r--r--editor/plugins/gradient_texture_2d_editor_plugin.cpp2
-rw-r--r--editor/plugins/node_3d_editor_gizmos.cpp2
-rw-r--r--editor/plugins/node_3d_editor_gizmos.h2
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp212
-rw-r--r--editor/plugins/node_3d_editor_plugin.h35
-rw-r--r--editor/plugins/particle_process_material_editor_plugin.cpp2
-rw-r--r--editor/plugins/physical_bone_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/polygon_2d_editor_plugin.cpp26
-rw-r--r--editor/plugins/script_editor_plugin.cpp107
-rw-r--r--editor/plugins/script_editor_plugin.h10
-rw-r--r--editor/plugins/script_text_editor.cpp28
-rw-r--r--editor/plugins/script_text_editor.h4
-rw-r--r--editor/plugins/shader_editor_plugin.cpp147
-rw-r--r--editor/plugins/shader_editor_plugin.h20
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.cpp184
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.h8
-rw-r--r--editor/plugins/style_box_editor_plugin.cpp2
-rw-r--r--editor/plugins/text_editor.cpp6
-rw-r--r--editor/plugins/text_editor.h2
-rw-r--r--editor/plugins/text_shader_editor.cpp3
-rw-r--r--editor/plugins/theme_editor_plugin.cpp19
-rw-r--r--editor/plugins/theme_editor_plugin.h1
-rw-r--r--editor/plugins/tiles/tile_data_editors.cpp2
-rw-r--r--editor/plugins/tiles/tile_map_layer_editor.cpp12
-rw-r--r--editor/plugins/version_control_editor_plugin.cpp2
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp24
-rw-r--r--editor/plugins/visual_shader_editor_plugin.h1
-rw-r--r--editor/progress_dialog.cpp8
-rw-r--r--editor/progress_dialog.h2
-rw-r--r--editor/project_manager.cpp1
-rw-r--r--editor/project_manager/project_dialog.cpp2
-rw-r--r--editor/project_manager/project_list.cpp2
-rw-r--r--editor/project_settings_editor.cpp4
-rw-r--r--editor/register_editor_types.cpp3
-rw-r--r--editor/rename_dialog.cpp2
-rw-r--r--editor/run_instances_dialog.cpp2
-rw-r--r--editor/scene_tree_dock.cpp22
-rw-r--r--editor/script_create_dialog.cpp2
-rw-r--r--editor/shader_create_dialog.cpp2
-rw-r--r--editor/shader_globals_editor.cpp2
-rw-r--r--editor/themes/editor_theme_manager.cpp48
-rw-r--r--gles3_builders.py18
-rw-r--r--glsl_builders.py28
-rw-r--r--godot.manifest5
-rw-r--r--main/main.cpp9
-rw-r--r--methods.py71
-rw-r--r--misc/dist/linux/org.godotengine.Godot.desktop4
-rw-r--r--misc/extension_api_validation/4.3-stable.expected39
-rw-r--r--modules/betsy/bc6h.glsl96
-rw-r--r--modules/betsy/image_compress_betsy.cpp27
-rw-r--r--modules/cvtt/image_compress_cvtt.cpp12
-rw-r--r--modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp2
-rw-r--r--modules/fbx/editor/editor_scene_importer_fbx2gltf.h2
-rw-r--r--modules/fbx/editor/editor_scene_importer_ufbx.cpp2
-rw-r--r--modules/fbx/editor/editor_scene_importer_ufbx.h2
-rw-r--r--modules/gdscript/gdscript.cpp83
-rw-r--r--modules/gdscript/gdscript.h7
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp20
-rw-r--r--modules/gdscript/gdscript_editor.cpp6
-rw-r--r--modules/gdscript/gdscript_function.h2
-rw-r--r--modules/gdscript/gdscript_parser.cpp47
-rw-r--r--modules/gdscript/gdscript_parser.h22
-rw-r--r--modules/gdscript/gdscript_vm.cpp85
-rw-r--r--modules/gdscript/gdscript_warning.cpp3
-rw-r--r--modules/gdscript/gdscript_warning.h2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd25
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd24
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd193
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd159
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.gd7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.notest.gd1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.out9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.out1
-rw-r--r--modules/gdscript/tests/scripts/completion/index/array_type.cfg9
-rw-r--r--modules/gdscript/tests/scripts/completion/index/array_type.gd10
-rw-r--r--modules/gdscript/tests/scripts/completion/index/array_value.cfg9
-rw-r--r--modules/gdscript/tests/scripts/completion/index/array_value.gd10
-rw-r--r--modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.cfg11
-rw-r--r--modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.gd13
-rw-r--r--modules/gdscript/tests/scripts/completion/index/dictionary_type.cfg9
-rw-r--r--modules/gdscript/tests/scripts/completion/index/dictionary_type.gd10
-rw-r--r--modules/gdscript/tests/scripts/completion/index/dictionary_value.cfg9
-rw-r--r--modules/gdscript/tests/scripts/completion/index/dictionary_value.gd10
-rw-r--r--modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.cfg11
-rw-r--r--modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.gd13
-rw-r--r--modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.cfg9
-rw-r--r--modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.gd13
-rw-r--r--modules/gdscript/tests/scripts/completion/index/untyped_local.cfg5
-rw-r--r--modules/gdscript/tests/scripts/completion/index/untyped_local.gd10
-rw-r--r--modules/gdscript/tests/scripts/completion/index/untyped_property.cfg5
-rw-r--r--modules/gdscript/tests/scripts/completion/index/untyped_property.gd9
-rw-r--r--modules/gdscript/tests/scripts/parser/features/annotations.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/class.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_arrays.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_enum.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.gd1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/truthiness.gd40
-rw-r--r--modules/gdscript/tests/scripts/parser/features/truthiness.out65
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd9
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd14
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/metatypes.gd1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd14
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd4
-rw-r--r--modules/gdscript/tests/scripts/utils.notest.gd18
-rw-r--r--modules/gltf/doc_classes/GLTFDocument.xml7
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp2
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.h2
-rw-r--r--modules/gltf/editor/editor_scene_importer_gltf.cpp2
-rw-r--r--modules/gltf/editor/editor_scene_importer_gltf.h2
-rw-r--r--modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp18
-rw-r--r--modules/gltf/gltf_document.cpp85
-rw-r--r--modules/gltf/gltf_document.h2
-rw-r--r--modules/gltf/tests/test_gltf_extras.h165
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp2
-rw-r--r--modules/mbedtls/tls_context_mbedtls.cpp2
-rw-r--r--modules/minimp3/audio_stream_mp3.cpp32
-rw-r--r--modules/minimp3/audio_stream_mp3.h2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs5
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs60
-rw-r--r--modules/mono/editor/hostfxr_resolver.cpp4
-rw-r--r--modules/navigation/2d/nav_mesh_generator_2d.cpp79
-rw-r--r--modules/navigation/3d/nav_mesh_generator_3d.cpp79
-rw-r--r--modules/navigation/nav_map.h13
-rw-r--r--modules/openxr/doc_classes/OpenXRAPIExtension.xml29
-rw-r--r--modules/openxr/extensions/openxr_composition_layer_extension.cpp2
-rw-r--r--modules/openxr/extensions/openxr_composition_layer_extension.h2
-rw-r--r--modules/openxr/extensions/openxr_debug_utils_extension.cpp287
-rw-r--r--modules/openxr/extensions/openxr_debug_utils_extension.h76
-rw-r--r--modules/openxr/extensions/openxr_extension_wrapper.h2
-rw-r--r--modules/openxr/openxr_api.cpp70
-rw-r--r--modules/openxr/openxr_api.h5
-rw-r--r--modules/openxr/openxr_api_extension.cpp28
-rw-r--r--modules/openxr/openxr_api_extension.h4
-rw-r--r--modules/openxr/register_types.cpp4
-rw-r--r--modules/regex/doc_classes/RegEx.xml6
-rw-r--r--modules/regex/regex.compat.inc46
-rw-r--r--modules/regex/regex.cpp21
-rw-r--r--modules/regex/regex.h10
-rw-r--r--modules/text_server_adv/text_server_adv.cpp720
-rw-r--r--modules/text_server_adv/text_server_adv.h13
-rw-r--r--modules/text_server_fb/text_server_fb.cpp656
-rw-r--r--modules/text_server_fb/text_server_fb.h13
-rw-r--r--platform/SCsub6
-rw-r--r--platform/android/display_server_android.cpp8
-rw-r--r--platform/android/export/export_plugin.cpp26
-rw-r--r--platform/android/export/export_plugin.h8
-rw-r--r--platform/android/file_access_android.cpp96
-rw-r--r--platform/android/file_access_android.h9
-rw-r--r--platform/android/file_access_filesystem_jandroid.cpp62
-rw-r--r--platform/android/file_access_filesystem_jandroid.h8
-rw-r--r--platform/android/java/editor/build.gradle2
-rw-r--r--platform/android/java/editor/src/main/AndroidManifest.xml5
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt192
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt23
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt175
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt93
-rw-r--r--platform/android/java/editor/src/main/res/drawable/ic_play_window_foreground.xml25
-rw-r--r--platform/android/java/editor/src/main/res/drawable/outline_fullscreen_exit_48.xml12
-rw-r--r--platform/android/java/editor/src/main/res/drawable/pip_button_activated_bg_drawable.xml10
-rw-r--r--platform/android/java/editor/src/main/res/drawable/pip_button_bg_drawable.xml9
-rw-r--r--platform/android/java/editor/src/main/res/drawable/pip_button_default_bg_drawable.xml10
-rw-r--r--platform/android/java/editor/src/main/res/layout/godot_game_layout.xml25
-rw-r--r--platform/android/java/editor/src/main/res/mipmap-anydpi-v26/ic_play_window.xml5
-rw-r--r--platform/android/java/editor/src/main/res/mipmap-hdpi/ic_play_window.pngbin0 -> 1954 bytes
-rw-r--r--platform/android/java/editor/src/main/res/mipmap-mdpi/ic_play_window.pngbin0 -> 1350 bytes
-rw-r--r--platform/android/java/editor/src/main/res/mipmap-xhdpi/ic_play_window.pngbin0 -> 2617 bytes
-rw-r--r--platform/android/java/editor/src/main/res/mipmap-xxhdpi/ic_play_window.pngbin0 -> 3923 bytes
-rw-r--r--platform/android/java/editor/src/main/res/mipmap-xxxhdpi/ic_play_window.pngbin0 -> 5480 bytes
-rw-r--r--platform/android/java/editor/src/main/res/values/dimens.xml2
-rw-r--r--platform/android/java/editor/src/main/res/values/strings.xml2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt114
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java27
-rw-r--r--platform/android/java_godot_lib_jni.cpp11
-rw-r--r--platform/android/java_godot_wrapper.cpp20
-rw-r--r--platform/android/java_godot_wrapper.h5
-rw-r--r--platform/ios/export/export_plugin.cpp16
-rw-r--r--platform/ios/export/export_plugin.h6
-rw-r--r--platform/linuxbsd/export/export_plugin.cpp9
-rw-r--r--platform/linuxbsd/export/export_plugin.h4
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.cpp27
-rw-r--r--platform/macos/export/export_plugin.cpp9
-rw-r--r--platform/macos/export/export_plugin.h4
-rw-r--r--platform/web/export/export_plugin.cpp9
-rw-r--r--platform/web/export/export_plugin.h6
-rw-r--r--platform/windows/console_wrapper_windows.cpp4
-rw-r--r--platform/windows/crash_handler_windows_seh.cpp2
-rw-r--r--platform/windows/detect.py161
-rw-r--r--platform/windows/display_server_windows.cpp46
-rw-r--r--platform/windows/export/export_plugin.cpp11
-rw-r--r--platform/windows/export/export_plugin.h6
-rw-r--r--platform/windows/godot_res_wrap.rc5
-rw-r--r--platform/windows/os_windows.cpp186
-rw-r--r--platform/windows/os_windows.h1
-rw-r--r--platform/windows/windows_utils.cpp6
-rw-r--r--scene/2d/audio_stream_player_2d.cpp9
-rw-r--r--scene/2d/parallax_2d.cpp18
-rw-r--r--scene/2d/parallax_2d.h1
-rw-r--r--scene/2d/remote_transform_2d.cpp10
-rw-r--r--scene/2d/tile_map_layer.cpp24
-rw-r--r--scene/2d/tile_map_layer.h6
-rw-r--r--scene/3d/audio_stream_player_3d.cpp9
-rw-r--r--scene/3d/cpu_particles_3d.cpp29
-rw-r--r--scene/3d/cpu_particles_3d.h3
-rw-r--r--scene/3d/physics/vehicle_body_3d.cpp10
-rw-r--r--scene/3d/physics/vehicle_body_3d.h4
-rw-r--r--scene/3d/remote_transform_3d.cpp10
-rw-r--r--scene/3d/voxelizer.h3
-rw-r--r--scene/animation/animation_mixer.cpp129
-rw-r--r--scene/animation/animation_mixer.h1
-rw-r--r--scene/animation/animation_tree.cpp7
-rw-r--r--scene/animation/animation_tree.h2
-rw-r--r--scene/animation/tween.cpp3
-rw-r--r--scene/audio/audio_stream_player.cpp9
-rw-r--r--scene/gui/base_button.cpp2
-rw-r--r--scene/gui/code_edit.cpp9
-rw-r--r--scene/gui/code_edit.h1
-rw-r--r--scene/gui/color_picker.cpp6
-rw-r--r--scene/gui/control.cpp4
-rw-r--r--scene/gui/file_dialog.cpp4
-rw-r--r--scene/gui/menu_bar.cpp3
-rw-r--r--scene/gui/menu_button.cpp3
-rw-r--r--scene/gui/rich_text_label.cpp139
-rw-r--r--scene/gui/rich_text_label.h10
-rw-r--r--scene/gui/spin_box.cpp224
-rw-r--r--scene/gui/spin_box.h67
-rw-r--r--scene/gui/text_edit.cpp55
-rw-r--r--scene/gui/text_edit.h30
-rw-r--r--scene/gui/tree.cpp63
-rw-r--r--scene/gui/tree.h4
-rw-r--r--scene/main/node.h2
-rw-r--r--scene/main/viewport.cpp4
-rw-r--r--scene/register_scene_types.cpp4
-rw-r--r--scene/resources/2d/navigation_polygon.h3
-rw-r--r--scene/resources/3d/fog_material.cpp3
-rw-r--r--scene/resources/3d/importer_mesh.cpp2
-rw-r--r--scene/resources/3d/sky_material.cpp11
-rw-r--r--scene/resources/animation.h18
-rw-r--r--scene/resources/audio_stream_wav.cpp25
-rw-r--r--scene/resources/audio_stream_wav.h2
-rw-r--r--scene/resources/navigation_mesh.h5
-rw-r--r--scene/resources/packed_scene.cpp12
-rw-r--r--scene/resources/packed_scene.h1
-rw-r--r--scene/resources/particle_process_material.cpp32
-rw-r--r--scene/resources/particle_process_material.h4
-rw-r--r--scene/resources/portable_compressed_texture.cpp2
-rw-r--r--scene/resources/shader.compat.inc46
-rw-r--r--scene/resources/shader.cpp9
-rw-r--r--scene/resources/shader.h12
-rw-r--r--scene/resources/visual_shader.cpp9
-rw-r--r--scene/resources/visual_shader.h2
-rw-r--r--scene/resources/visual_shader_nodes.compat.inc63
-rw-r--r--scene/resources/visual_shader_nodes.cpp13
-rw-r--r--scene/resources/visual_shader_nodes.h25
-rw-r--r--scene/scene_string_names.cpp1
-rw-r--r--scene/scene_string_names.h1
-rw-r--r--scene/theme/default_theme.cpp37
-rw-r--r--scene/theme/icons/default_theme_icons_builders.py20
-rw-r--r--scene/theme/icons/value_down.svg1
-rw-r--r--scene/theme/icons/value_up.svg1
-rw-r--r--servers/audio/effects/audio_effect_record.cpp2
-rw-r--r--servers/audio_server.cpp18
-rw-r--r--servers/navigation/navigation_globals.h66
-rw-r--r--servers/navigation_server_3d.cpp15
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp2
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp1
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp1
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl2
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl2
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.h2
-rw-r--r--servers/rendering/renderer_rd/storage_rd/texture_storage.cpp20
-rw-r--r--servers/rendering/rendering_device.cpp9
-rw-r--r--servers/rendering/rendering_device.h4
-rw-r--r--servers/rendering/rendering_device_binds.cpp2
-rw-r--r--servers/rendering/shader_language.cpp39
-rw-r--r--servers/rendering/shader_language.h10
-rw-r--r--servers/rendering/shader_preprocessor.cpp2
-rw-r--r--servers/rendering/shader_types.cpp3
-rw-r--r--servers/rendering_server.cpp1
-rw-r--r--servers/rendering_server.h2
-rw-r--r--servers/text_server.cpp18
-rw-r--r--tests/core/io/test_json_native.h160
-rw-r--r--tests/core/object/test_class_db.h4
-rw-r--r--tests/core/object/test_object.h25
-rw-r--r--tests/core/string/test_string.h59
-rw-r--r--tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl28
-rw-r--r--tests/python_build/fixtures/glsl/compute_expected_full.glsl14
-rw-r--r--tests/python_build/fixtures/glsl/vertex_fragment_expected_full.glsl34
-rw-r--r--tests/python_build/fixtures/rd_glsl/compute_expected_full.glsl14
-rw-r--r--tests/python_build/fixtures/rd_glsl/vertex_fragment_expected_full.glsl27
-rw-r--r--tests/scene/test_audio_stream_wav.h2
-rw-r--r--tests/scene/test_button.h66
-rw-r--r--tests/scene/test_gradient_texture.h87
-rw-r--r--tests/scene/test_node_2d.h125
-rw-r--r--tests/scene/test_option_button.h136
-rw-r--r--tests/scene/test_style_box_texture.h194
-rw-r--r--tests/scene/test_tree.h175
-rw-r--r--tests/test_main.cpp6
-rw-r--r--thirdparty/README.md10
-rw-r--r--thirdparty/libwebp/patches/godot-clang-cl-fix.patch19
-rw-r--r--thirdparty/libwebp/src/dsp/cpu.h4
-rw-r--r--thirdparty/mbedtls/include/mbedtls/bignum.h2
-rw-r--r--thirdparty/mbedtls/include/mbedtls/build_info.h30
-rw-r--r--thirdparty/mbedtls/include/mbedtls/check_config.h14
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h29
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h16
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h10
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h10
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h12
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h12
-rw-r--r--thirdparty/mbedtls/include/mbedtls/config_psa.h6
-rw-r--r--thirdparty/mbedtls/include/mbedtls/ctr_drbg.h33
-rw-r--r--thirdparty/mbedtls/include/mbedtls/ecdh.h2
-rw-r--r--thirdparty/mbedtls/include/mbedtls/ecp.h2
-rw-r--r--thirdparty/mbedtls/include/mbedtls/mbedtls_config.h64
-rw-r--r--thirdparty/mbedtls/include/mbedtls/pk.h60
-rw-r--r--thirdparty/mbedtls/include/mbedtls/ssl.h145
-rw-r--r--thirdparty/mbedtls/include/psa/crypto.h188
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h10
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_adjust_config_dependencies.h51
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h10
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h10
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_extra.h10
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_struct.h28
-rw-r--r--thirdparty/mbedtls/include/psa/crypto_types.h26
-rw-r--r--thirdparty/mbedtls/library/bignum.c31
-rw-r--r--thirdparty/mbedtls/library/bignum_core.c163
-rw-r--r--thirdparty/mbedtls/library/bignum_core.h70
-rw-r--r--thirdparty/mbedtls/library/bignum_internal.h50
-rw-r--r--thirdparty/mbedtls/library/block_cipher.c4
-rw-r--r--thirdparty/mbedtls/library/cipher.c3
-rw-r--r--thirdparty/mbedtls/library/common.h16
-rw-r--r--thirdparty/mbedtls/library/ctr_drbg.c128
-rw-r--r--thirdparty/mbedtls/library/entropy.c4
-rw-r--r--thirdparty/mbedtls/library/entropy_poll.c4
-rw-r--r--thirdparty/mbedtls/library/error.c2
-rw-r--r--thirdparty/mbedtls/library/lmots.c8
-rw-r--r--thirdparty/mbedtls/library/lms.c8
-rw-r--r--thirdparty/mbedtls/library/md.c6
-rw-r--r--thirdparty/mbedtls/library/net_sockets.c2
-rw-r--r--thirdparty/mbedtls/library/nist_kw.c4
-rw-r--r--thirdparty/mbedtls/library/pem.c4
-rw-r--r--thirdparty/mbedtls/library/pk.c46
-rw-r--r--thirdparty/mbedtls/library/platform_util.c2
-rw-r--r--thirdparty/mbedtls/library/psa_crypto_core.h128
-rw-r--r--thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h9
-rw-r--r--thirdparty/mbedtls/library/psa_crypto_random_impl.h17
-rw-r--r--thirdparty/mbedtls/library/psa_crypto_rsa.h14
-rw-r--r--thirdparty/mbedtls/library/psa_crypto_slot_management.h79
-rw-r--r--thirdparty/mbedtls/library/rsa.c3
-rw-r--r--thirdparty/mbedtls/library/sha256.c4
-rw-r--r--thirdparty/mbedtls/library/ssl_cookie.c4
-rw-r--r--thirdparty/mbedtls/library/ssl_debug_helpers_generated.c2
-rw-r--r--thirdparty/mbedtls/library/ssl_misc.h96
-rw-r--r--thirdparty/mbedtls/library/ssl_msg.c39
-rw-r--r--thirdparty/mbedtls/library/ssl_ticket.c4
-rw-r--r--thirdparty/mbedtls/library/ssl_tls.c617
-rw-r--r--thirdparty/mbedtls/library/ssl_tls12_client.c6
-rw-r--r--thirdparty/mbedtls/library/ssl_tls12_server.c25
-rw-r--r--thirdparty/mbedtls/library/ssl_tls13_client.c7
-rw-r--r--thirdparty/mbedtls/library/ssl_tls13_generic.c169
-rw-r--r--thirdparty/mbedtls/library/ssl_tls13_server.c29
-rw-r--r--thirdparty/mbedtls/library/version_features.c3
-rw-r--r--thirdparty/mbedtls/library/x509_crt.c2
-rw-r--r--thirdparty/mbedtls/library/x509write_crt.c4
-rw-r--r--thirdparty/mbedtls/library/x509write_csr.c4
-rw-r--r--thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff20
-rw-r--r--thirdparty/mbedtls/patches/no-flexible-arrays.diff132
-rw-r--r--thirdparty/misc/patches/qoa-min-fix.patch14
-rw-r--r--thirdparty/misc/qoa.h6
-rw-r--r--thirdparty/thorvg/inc/config.h2
-rw-r--r--thirdparty/thorvg/inc/thorvg.h150
-rw-r--r--thirdparty/thorvg/patches/pr2702-sw_engine-handle-small-cubics.patch96
-rw-r--r--thirdparty/thorvg/src/common/tvgArray.h2
-rw-r--r--thirdparty/thorvg/src/common/tvgCompressor.cpp2
-rw-r--r--thirdparty/thorvg/src/common/tvgInlist.h2
-rw-r--r--thirdparty/thorvg/src/common/tvgLines.cpp2
-rw-r--r--thirdparty/thorvg/src/common/tvgLock.h2
-rw-r--r--thirdparty/thorvg/src/common/tvgMath.cpp8
-rw-r--r--thirdparty/thorvg/src/common/tvgMath.h20
-rw-r--r--thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp13
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h43
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp70
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwImage.cpp68
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp27
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp108
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h112
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp127
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h9
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp81
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp38
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp27
-rw-r--r--thirdparty/thorvg/src/renderer/tvgAccessor.cpp41
-rw-r--r--thirdparty/thorvg/src/renderer/tvgAnimation.cpp2
-rw-r--r--thirdparty/thorvg/src/renderer/tvgCanvas.h6
-rw-r--r--thirdparty/thorvg/src/renderer/tvgInitializer.cpp42
-rw-r--r--thirdparty/thorvg/src/renderer/tvgLoadModule.h3
-rw-r--r--thirdparty/thorvg/src/renderer/tvgPaint.cpp144
-rw-r--r--thirdparty/thorvg/src/renderer/tvgPaint.h63
-rw-r--r--thirdparty/thorvg/src/renderer/tvgPicture.cpp45
-rw-r--r--thirdparty/thorvg/src/renderer/tvgPicture.h80
-rw-r--r--thirdparty/thorvg/src/renderer/tvgRender.cpp35
-rw-r--r--thirdparty/thorvg/src/renderer/tvgRender.h97
-rw-r--r--thirdparty/thorvg/src/renderer/tvgScene.h30
-rw-r--r--thirdparty/thorvg/src/renderer/tvgShape.h82
-rw-r--r--thirdparty/thorvg/src/renderer/tvgTaskScheduler.h2
-rw-r--r--thirdparty/thorvg/src/renderer/tvgText.cpp13
-rw-r--r--thirdparty/thorvg/src/renderer/tvgText.h74
-rwxr-xr-xthirdparty/thorvg/update-thorvg.sh18
646 files changed, 14179 insertions, 6835 deletions
diff --git a/.gitattributes b/.gitattributes
index 5af3e121a8..30d1acb497 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,6 +1,6 @@
# Properly detect languages on Github
-*.h linguist-language=cpp
-*.inc linguist-language=cpp
+*.h linguist-language=C++
+*.inc linguist-language=C++
thirdparty/* linguist-vendored
# Normalize EOL for all files that Git considers text files
diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml
index 90629204e6..0c21576517 100644
--- a/.github/workflows/windows_builds.yml
+++ b/.github/workflows/windows_builds.yml
@@ -30,6 +30,14 @@ jobs:
# Skip debug symbols, they're way too big with MSVC.
sconsflags: debug_symbols=no vsproj=yes vsproj_gen_only=no windows_subsystem=console
bin: "./bin/godot.windows.editor.x86_64.exe"
+ artifact: true
+
+ - name: Editor w/ clang-cl (target=editor, tests=yes, use_llvm=yes)
+ cache-name: windows-editor-clang
+ target: editor
+ tests: true
+ sconsflags: debug_symbols=no windows_subsystem=console use_llvm=yes
+ bin: ./bin/godot.windows.editor.x86_64.llvm.exe
- name: Template (target=template_release)
cache-name: windows-template
@@ -37,6 +45,7 @@ jobs:
tests: true
sconsflags: debug_symbols=no tests=yes
bin: "./bin/godot.windows.template_release.x86_64.console.exe"
+ artifact: true
steps:
- uses: actions/checkout@v4
@@ -84,10 +93,12 @@ jobs:
continue-on-error: true
- name: Prepare artifact
+ if: ${{ matrix.artifact }}
run: |
Remove-Item bin/* -Include *.exp,*.lib,*.pdb -Force
- name: Upload artifact
+ if: ${{ matrix.artifact }}
uses: ./.github/actions/upload-artifact
with:
name: ${{ matrix.cache-name }}
diff --git a/SConstruct b/SConstruct
index d2cfe2732c..0297cd6e61 100644
--- a/SConstruct
+++ b/SConstruct
@@ -237,6 +237,11 @@ opts.Add(BoolVariable("ninja", "Use the ninja backend for faster rebuilds", Fals
opts.Add(BoolVariable("ninja_auto_run", "Run ninja automatically after generating the ninja file", True))
opts.Add("ninja_file", "Path to the generated ninja file", "build.ninja")
opts.Add(BoolVariable("compiledb", "Generate compilation DB (`compile_commands.json`) for external tools", False))
+opts.Add(
+ "num_jobs",
+ "Use up to N jobs when compiling (equivalent to `-j N`). Defaults to max jobs - 1. Ignored if -j is used.",
+ "",
+)
opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False))
opts.Add(BoolVariable("progress", "Show a progress indicator during compilation", True))
opts.Add(EnumVariable("warnings", "Level of compilation warnings", "all", ("extra", "all", "moderate", "no")))
@@ -540,16 +545,22 @@ initial_num_jobs = env.GetOption("num_jobs")
altered_num_jobs = initial_num_jobs + 1
env.SetOption("num_jobs", altered_num_jobs)
if env.GetOption("num_jobs") == altered_num_jobs:
- cpu_count = os.cpu_count()
- if cpu_count is None:
- print_warning("Couldn't auto-detect CPU count to configure build parallelism. Specify it with the -j argument.")
+ num_jobs = env.get("num_jobs", "")
+ if str(num_jobs).isdigit() and int(num_jobs) > 0:
+ env.SetOption("num_jobs", num_jobs)
else:
- safer_cpu_count = cpu_count if cpu_count <= 4 else cpu_count - 1
- print(
- "Auto-detected %d CPU cores available for build parallelism. Using %d cores by default. You can override it with the -j argument."
- % (cpu_count, safer_cpu_count)
- )
- env.SetOption("num_jobs", safer_cpu_count)
+ cpu_count = os.cpu_count()
+ if cpu_count is None:
+ print_warning(
+ "Couldn't auto-detect CPU count to configure build parallelism. Specify it with the `-j` or `num_jobs` arguments."
+ )
+ else:
+ safer_cpu_count = cpu_count if cpu_count <= 4 else cpu_count - 1
+ print(
+ "Auto-detected %d CPU cores available for build parallelism. Using %d cores by default. You can override it with the `-j` or `num_jobs` arguments."
+ % (cpu_count, safer_cpu_count)
+ )
+ env.SetOption("num_jobs", safer_cpu_count)
env.extra_suffix = ""
@@ -792,7 +803,7 @@ elif env.msvc:
env.Append(CXXFLAGS=["/EHsc"])
# Configure compiler warnings
-if env.msvc: # MSVC
+if env.msvc and not methods.using_clang(env): # MSVC
if env["warnings"] == "no":
env.Append(CCFLAGS=["/w"])
else:
@@ -842,8 +853,11 @@ else: # GCC, Clang
# for putting them in `Set` or `Map`. We don't mind about unreliable ordering.
common_warnings += ["-Wno-ordered-compare-function-pointers"]
+ # clang-cl will interpret `-Wall` as `-Weverything`, workaround with compatibility cast
+ W_ALL = "-Wall" if not env.msvc else "-W3"
+
if env["warnings"] == "extra":
- env.Append(CCFLAGS=["-Wall", "-Wextra", "-Wwrite-strings", "-Wno-unused-parameter"] + common_warnings)
+ env.Append(CCFLAGS=[W_ALL, "-Wextra", "-Wwrite-strings", "-Wno-unused-parameter"] + common_warnings)
env.Append(CXXFLAGS=["-Wctor-dtor-privacy", "-Wnon-virtual-dtor"])
if methods.using_gcc(env):
env.Append(
@@ -865,9 +879,9 @@ else: # GCC, Clang
elif methods.using_clang(env) or methods.using_emcc(env):
env.Append(CCFLAGS=["-Wimplicit-fallthrough"])
elif env["warnings"] == "all":
- env.Append(CCFLAGS=["-Wall"] + common_warnings)
+ env.Append(CCFLAGS=[W_ALL] + common_warnings)
elif env["warnings"] == "moderate":
- env.Append(CCFLAGS=["-Wall", "-Wno-unused"] + common_warnings)
+ env.Append(CCFLAGS=[W_ALL, "-Wno-unused"] + common_warnings)
else: # 'no'
env.Append(CCFLAGS=["-w"])
@@ -1021,7 +1035,9 @@ if env["vsproj"]:
if env["compiledb"]:
if env.scons_version < (4, 0, 0):
# Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later.
- print_error("The `compiledb=yes` option requires SCons 4.0 or later, but your version is %s." % scons_raw_version)
+ print_error(
+ "The `compiledb=yes` option requires SCons 4.0 or later, but your version is %s." % scons_raw_version
+ )
Exit(255)
env.Tool("compilation_db")
diff --git a/core/SCsub b/core/SCsub
index 1bd4eae16c..c8267ae960 100644
--- a/core/SCsub
+++ b/core/SCsub
@@ -140,7 +140,7 @@ if env["builtin_zstd"]:
"decompress/zstd_decompress_block.c",
"decompress/zstd_decompress.c",
]
- if env["platform"] in ["android", "ios", "linuxbsd", "macos"]:
+ if env["platform"] in ["android", "ios", "linuxbsd", "macos"] and env["arch"] == "x86_64":
# Match platforms with ZSTD_ASM_SUPPORTED in common/portability_macros.h
thirdparty_zstd_sources.append("decompress/huf_decompress_amd64.S")
thirdparty_zstd_sources = [thirdparty_zstd_dir + file for file in thirdparty_zstd_sources]
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index 5b04986020..32f36e01f9 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -1472,10 +1472,6 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_height_override", PROPERTY_HINT_RANGE, "0,4320,1,or_greater"), 0); // 8K resolution
GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true);
-#ifdef TOOLS_ENABLED
- GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor_hint", false);
-#endif
-
GLOBAL_DEF("animation/warnings/check_invalid_track_paths", true);
GLOBAL_DEF("animation/warnings/check_angle_interpolation_type_conflicting", true);
diff --git a/core/core_bind.compat.inc b/core/core_bind.compat.inc
new file mode 100644
index 0000000000..83b7b33e38
--- /dev/null
+++ b/core/core_bind.compat.inc
@@ -0,0 +1,45 @@
+/**************************************************************************/
+/* core_bind.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
+
+namespace core_bind {
+
+void Semaphore::_post_bind_compat_93605() {
+ post(1);
+}
+
+void Semaphore::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("post"), &Semaphore::_post_bind_compat_93605);
+}
+
+}; // namespace core_bind
+
+#endif
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 84f66c3479..4172793f9d 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "core_bind.h"
+#include "core_bind.compat.inc"
#include "core/config/project_settings.h"
#include "core/crypto/crypto_core.h"
@@ -115,6 +116,11 @@ bool ResourceLoader::has_cached(const String &p_path) {
return ResourceCache::has(local_path);
}
+Ref<Resource> ResourceLoader::get_cached_ref(const String &p_path) {
+ String local_path = ProjectSettings::get_singleton()->localize_path(p_path);
+ return ResourceCache::get_ref(local_path);
+}
+
bool ResourceLoader::exists(const String &p_path, const String &p_type_hint) {
return ::ResourceLoader::exists(p_path, p_type_hint);
}
@@ -135,6 +141,7 @@ void ResourceLoader::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_abort_on_missing_resources", "abort"), &ResourceLoader::set_abort_on_missing_resources);
ClassDB::bind_method(D_METHOD("get_dependencies", "path"), &ResourceLoader::get_dependencies);
ClassDB::bind_method(D_METHOD("has_cached", "path"), &ResourceLoader::has_cached);
+ ClassDB::bind_method(D_METHOD("get_cached_ref", "path"), &ResourceLoader::get_cached_ref);
ClassDB::bind_method(D_METHOD("exists", "path", "type_hint"), &ResourceLoader::exists, DEFVAL(""));
ClassDB::bind_method(D_METHOD("get_resource_uid", "path"), &ResourceLoader::get_resource_uid);
@@ -1210,14 +1217,15 @@ bool Semaphore::try_wait() {
return semaphore.try_wait();
}
-void Semaphore::post() {
- semaphore.post();
+void Semaphore::post(int p_count) {
+ ERR_FAIL_COND(p_count <= 0);
+ semaphore.post(p_count);
}
void Semaphore::_bind_methods() {
ClassDB::bind_method(D_METHOD("wait"), &Semaphore::wait);
ClassDB::bind_method(D_METHOD("try_wait"), &Semaphore::try_wait);
- ClassDB::bind_method(D_METHOD("post"), &Semaphore::post);
+ ClassDB::bind_method(D_METHOD("post", "count"), &Semaphore::post, DEFVAL(1));
}
////// Mutex //////
@@ -1768,7 +1776,7 @@ Object *Engine::get_singleton_object(const StringName &p_name) const {
void Engine::register_singleton(const StringName &p_name, Object *p_object) {
ERR_FAIL_COND_MSG(has_singleton(p_name), "Singleton already registered: " + String(p_name));
- ERR_FAIL_COND_MSG(!String(p_name).is_valid_identifier(), "Singleton name is not a valid identifier: " + p_name);
+ ERR_FAIL_COND_MSG(!String(p_name).is_valid_ascii_identifier(), "Singleton name is not a valid identifier: " + p_name);
::Engine::Singleton s;
s.class_name = p_name;
s.name = p_name;
diff --git a/core/core_bind.h b/core/core_bind.h
index 0949ba628f..122963e634 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -83,6 +83,7 @@ public:
void set_abort_on_missing_resources(bool p_abort);
PackedStringArray get_dependencies(const String &p_path);
bool has_cached(const String &p_path);
+ Ref<Resource> get_cached_ref(const String &p_path);
bool exists(const String &p_path, const String &p_type_hint = "");
ResourceUID::ID get_resource_uid(const String &p_path);
@@ -390,12 +391,17 @@ class Semaphore : public RefCounted {
GDCLASS(Semaphore, RefCounted);
::Semaphore semaphore;
+protected:
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ void _post_bind_compat_93605();
+ static void _bind_compatibility_methods();
+#endif // DISABLE_DEPRECATED
public:
void wait();
bool try_wait();
- void post();
+ void post(int p_count = 1);
};
class Thread : public RefCounted {
diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp
index 21a9014626..9dca47a0b4 100644
--- a/core/debugger/remote_debugger_peer.cpp
+++ b/core/debugger/remote_debugger_peer.cpp
@@ -144,9 +144,8 @@ void RemoteDebuggerPeerTCP::_read_in() {
Error err = decode_variant(var, buf, in_pos, &read);
ERR_CONTINUE(read != in_pos || err != OK);
ERR_CONTINUE_MSG(var.get_type() != Variant::ARRAY, "Malformed packet received, not an Array.");
- mutex.lock();
+ MutexLock lock(mutex);
in_queue.push_back(var);
- mutex.unlock();
}
}
}
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index 940a34396f..e764b9c112 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -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_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier.");
+ 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(ClassDB::class_exists(class_name), "Attempt to register extension class '" + class_name + "', which appears to be already registered.");
Extension *parent_extension = nullptr;
@@ -678,12 +678,10 @@ Error GDExtension::open_library(const String &p_path, const Ref<GDExtensionLoade
ERR_FAIL_NULL_V_MSG(p_loader, FAILED, "Can't open GDExtension without a loader.");
loader = p_loader;
- String abs_path = ProjectSettings::get_singleton()->globalize_path(p_path);
+ Error err = loader->open_library(p_path);
- Error err = loader->open_library(abs_path);
-
- ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + abs_path);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + abs_path);
+ ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + p_path);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + p_path);
err = loader->initialize(&gdextension_get_proc_address, this, &initialization);
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index a5a0fc906a..0ebe86d0a7 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -1299,7 +1299,7 @@ static void gdextension_object_call_script_method(GDExtensionObjectPtr p_object,
const StringName method = *reinterpret_cast<const StringName *>(p_method);
const Variant **args = (const Variant **)p_args;
- Callable::CallError error;
+ Callable::CallError error; // TODO: Check `error`?
memnew_placement(r_return, Variant);
*(Variant *)r_return = o->callp(method, args, p_argument_count, error);
diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h
index cac76d39bd..9057e04bf3 100644
--- a/core/extension/gdextension_interface.h
+++ b/core/extension/gdextension_interface.h
@@ -268,7 +268,7 @@ typedef void (*GDExtensionClassReference)(GDExtensionClassInstancePtr p_instance
typedef void (*GDExtensionClassUnreference)(GDExtensionClassInstancePtr p_instance);
typedef void (*GDExtensionClassCallVirtual)(GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret);
typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance)(void *p_class_userdata);
-typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance2)(void *p_class_userdata, bool p_notify_postinitialize);
+typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance2)(void *p_class_userdata, GDExtensionBool p_notify_postinitialize);
typedef void (*GDExtensionClassFreeInstance)(void *p_class_userdata, GDExtensionClassInstancePtr p_instance);
typedef GDExtensionClassInstancePtr (*GDExtensionClassRecreateInstance)(void *p_class_userdata, GDExtensionObjectPtr p_object);
typedef GDExtensionClassCallVirtual (*GDExtensionClassGetVirtual)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name);
diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp
index eeae6b1996..01efe0d96e 100644
--- a/core/extension/gdextension_manager.cpp
+++ b/core/extension/gdextension_manager.cpp
@@ -32,14 +32,18 @@
#include "core/extension/gdextension_compat_hashes.h"
#include "core/extension/gdextension_library_loader.h"
+#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "core/object/script_language.h"
-GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension) {
+GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load) {
if (level >= 0) { // Already initialized up to some level.
- int32_t minimum_level = p_extension->get_minimum_library_initialization_level();
- if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) {
- return LOAD_STATUS_NEEDS_RESTART;
+ int32_t minimum_level = 0;
+ if (!p_first_load) {
+ minimum_level = p_extension->get_minimum_library_initialization_level();
+ if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) {
+ return LOAD_STATUS_NEEDS_RESTART;
+ }
}
// Initialize up to current level.
for (int32_t i = minimum_level; i <= level; i++) {
@@ -51,10 +55,20 @@ GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(cons
gdextension_class_icon_paths[kv.key] = kv.value;
}
+#ifdef TOOLS_ENABLED
+ // Signals that a new extension is loaded so GDScript can register new class names.
+ emit_signal("extension_loaded", p_extension);
+#endif
+
return LOAD_STATUS_OK;
}
GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(const Ref<GDExtension> &p_extension) {
+#ifdef TOOLS_ENABLED
+ // Signals that a new extension is unloading so GDScript can unregister class names.
+ emit_signal("extension_unloading", p_extension);
+#endif
+
if (level >= 0) { // Already initialized up to some level.
// Deinitialize down from current level.
for (int32_t i = level; i >= GDExtension::INITIALIZATION_LEVEL_CORE; i--) {
@@ -89,7 +103,7 @@ GDExtensionManager::LoadStatus GDExtensionManager::load_extension_with_loader(co
return LOAD_STATUS_FAILED;
}
- LoadStatus status = _load_extension_internal(extension);
+ LoadStatus status = _load_extension_internal(extension, true);
if (status != LOAD_STATUS_OK) {
return status;
}
@@ -135,7 +149,7 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String
return LOAD_STATUS_FAILED;
}
- status = _load_extension_internal(extension);
+ status = _load_extension_internal(extension, false);
if (status != LOAD_STATUS_OK) {
return status;
}
@@ -274,6 +288,71 @@ void GDExtensionManager::reload_extensions() {
#endif
}
+bool GDExtensionManager::ensure_extensions_loaded(const HashSet<String> &p_extensions) {
+ Vector<String> extensions_added;
+ Vector<String> extensions_removed;
+
+ for (const String &E : p_extensions) {
+ if (!is_extension_loaded(E)) {
+ extensions_added.push_back(E);
+ }
+ }
+
+ Vector<String> loaded_extensions = get_loaded_extensions();
+ for (const String &loaded_extension : loaded_extensions) {
+ if (!p_extensions.has(loaded_extension)) {
+ // The extension may not have a .gdextension file.
+ if (!FileAccess::exists(loaded_extension)) {
+ extensions_removed.push_back(loaded_extension);
+ }
+ }
+ }
+
+ String extension_list_config_file = GDExtension::get_extension_list_config_file();
+ if (p_extensions.size()) {
+ if (extensions_added.size() || extensions_removed.size()) {
+ // Extensions were added or removed.
+ Ref<FileAccess> f = FileAccess::open(extension_list_config_file, FileAccess::WRITE);
+ for (const String &E : p_extensions) {
+ f->store_line(E);
+ }
+ }
+ } else {
+ if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) {
+ // Extensions were removed.
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ da->remove(extension_list_config_file);
+ }
+ }
+
+ bool needs_restart = false;
+ for (const String &extension : extensions_added) {
+ GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extension);
+ if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
+ needs_restart = true;
+ }
+ }
+
+ for (const String &extension : extensions_removed) {
+ GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extension);
+ if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
+ needs_restart = true;
+ }
+ }
+
+#ifdef TOOLS_ENABLED
+ if (extensions_added.size() || extensions_removed.size()) {
+ // Emitting extensions_reloaded so EditorNode can reload Inspector and regenerate documentation.
+ emit_signal("extensions_reloaded");
+
+ // Reload all scripts to clear out old references.
+ callable_mp_static(&GDExtensionManager::_reload_all_scripts).call_deferred();
+ }
+#endif
+
+ return needs_restart;
+}
+
GDExtensionManager *GDExtensionManager::get_singleton() {
return singleton;
}
@@ -294,6 +373,8 @@ void GDExtensionManager::_bind_methods() {
BIND_ENUM_CONSTANT(LOAD_STATUS_NEEDS_RESTART);
ADD_SIGNAL(MethodInfo("extensions_reloaded"));
+ ADD_SIGNAL(MethodInfo("extension_loaded", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension")));
+ ADD_SIGNAL(MethodInfo("extension_unloading", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension")));
}
GDExtensionManager *GDExtensionManager::singleton = nullptr;
diff --git a/core/extension/gdextension_manager.h b/core/extension/gdextension_manager.h
index b488189604..39a600474c 100644
--- a/core/extension/gdextension_manager.h
+++ b/core/extension/gdextension_manager.h
@@ -54,7 +54,7 @@ public:
};
private:
- LoadStatus _load_extension_internal(const Ref<GDExtension> &p_extension);
+ LoadStatus _load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load);
LoadStatus _unload_extension_internal(const Ref<GDExtension> &p_extension);
#ifdef TOOLS_ENABLED
@@ -85,6 +85,7 @@ public:
void load_extensions();
void reload_extensions();
+ bool ensure_extensions_loaded(const HashSet<String> &p_extensions);
GDExtensionManager();
~GDExtensionManager();
diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp
index c857d54925..d919243e6b 100644
--- a/core/io/file_access.cpp
+++ b/core/io/file_access.cpp
@@ -223,59 +223,44 @@ String FileAccess::fix_path(const String &p_path) const {
}
/* these are all implemented for ease of porting, then can later be optimized */
+uint8_t FileAccess::get_8() const {
+ uint8_t data = 0;
+ get_buffer(&data, sizeof(uint8_t));
-uint16_t FileAccess::get_16() const {
- uint16_t res;
- uint8_t a, b;
+ return data;
+}
- a = get_8();
- b = get_8();
+uint16_t FileAccess::get_16() const {
+ uint16_t data = 0;
+ get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint16_t));
if (big_endian) {
- SWAP(a, b);
+ data = BSWAP16(data);
}
- res = b;
- res <<= 8;
- res |= a;
-
- return res;
+ return data;
}
uint32_t FileAccess::get_32() const {
- uint32_t res;
- uint16_t a, b;
-
- a = get_16();
- b = get_16();
+ uint32_t data = 0;
+ get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint32_t));
if (big_endian) {
- SWAP(a, b);
+ data = BSWAP32(data);
}
- res = b;
- res <<= 16;
- res |= a;
-
- return res;
+ return data;
}
uint64_t FileAccess::get_64() const {
- uint64_t res;
- uint32_t a, b;
-
- a = get_32();
- b = get_32();
+ uint64_t data = 0;
+ get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint64_t));
if (big_endian) {
- SWAP(a, b);
+ data = BSWAP64(data);
}
- res = b;
- res <<= 32;
- res |= a;
-
- return res;
+ return data;
}
float FileAccess::get_float() const {
@@ -465,17 +450,6 @@ String FileAccess::get_as_text(bool p_skip_cr) const {
return text;
}
-uint64_t FileAccess::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
-
- uint64_t i = 0;
- for (i = 0; i < p_length && !eof_reached(); i++) {
- p_dst[i] = get_8();
- }
-
- return i;
-}
-
Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const {
Vector<uint8_t> data;
@@ -488,7 +462,7 @@ Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const {
ERR_FAIL_COND_V_MSG(err != OK, data, "Can't resize data to " + itos(p_length) + " elements.");
uint8_t *w = data.ptrw();
- int64_t len = get_buffer(&w[0], p_length);
+ int64_t len = get_buffer(w, p_length);
if (len < p_length) {
data.resize(len);
@@ -512,46 +486,32 @@ String FileAccess::get_as_utf8_string(bool p_skip_cr) const {
return s;
}
-void FileAccess::store_16(uint16_t p_dest) {
- uint8_t a, b;
-
- a = p_dest & 0xFF;
- b = p_dest >> 8;
+void FileAccess::store_8(uint8_t p_dest) {
+ store_buffer(&p_dest, sizeof(uint8_t));
+}
+void FileAccess::store_16(uint16_t p_dest) {
if (big_endian) {
- SWAP(a, b);
+ p_dest = BSWAP16(p_dest);
}
- store_8(a);
- store_8(b);
+ store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint16_t));
}
void FileAccess::store_32(uint32_t p_dest) {
- uint16_t a, b;
-
- a = p_dest & 0xFFFF;
- b = p_dest >> 16;
-
if (big_endian) {
- SWAP(a, b);
+ p_dest = BSWAP32(p_dest);
}
- store_16(a);
- store_16(b);
+ store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint32_t));
}
void FileAccess::store_64(uint64_t p_dest) {
- uint32_t a, b;
-
- a = p_dest & 0xFFFFFFFF;
- b = p_dest >> 32;
-
if (big_endian) {
- SWAP(a, b);
+ p_dest = BSWAP64(p_dest);
}
- store_32(a);
- store_32(b);
+ store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint64_t));
}
void FileAccess::store_real(real_t p_real) {
@@ -708,22 +668,11 @@ void FileAccess::store_csv_line(const Vector<String> &p_values, const String &p_
store_line(line);
}
-void FileAccess::store_buffer(const uint8_t *p_src, uint64_t p_length) {
- ERR_FAIL_COND(!p_src && p_length > 0);
- for (uint64_t i = 0; i < p_length; i++) {
- store_8(p_src[i]);
- }
-}
-
void FileAccess::store_buffer(const Vector<uint8_t> &p_buffer) {
uint64_t len = p_buffer.size();
- if (len == 0) {
- return;
- }
-
const uint8_t *r = p_buffer.ptr();
- store_buffer(&r[0], len);
+ store_buffer(r, len);
}
void FileAccess::store_var(const Variant &p_var, bool p_full_objects) {
diff --git a/core/io/file_access.h b/core/io/file_access.h
index 2ab84db4b6..2f4d1a8604 100644
--- a/core/io/file_access.h
+++ b/core/io/file_access.h
@@ -137,7 +137,7 @@ public:
virtual bool eof_reached() const = 0; ///< reading passed EOF
- virtual uint8_t get_8() const = 0; ///< get a byte
+ virtual uint8_t get_8() const; ///< get a byte
virtual uint16_t get_16() const; ///< get 16 bits uint
virtual uint32_t get_32() const; ///< get 32 bits uint
virtual uint64_t get_64() const; ///< get 64 bits uint
@@ -148,7 +148,7 @@ public:
Variant get_var(bool p_allow_objects = false) const;
- virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const; ///< get an array of bytes
+ virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const = 0; ///< get an array of bytes, needs to be overwritten by children.
Vector<uint8_t> get_buffer(int64_t p_length) const;
virtual String get_line() const;
virtual String get_token() const;
@@ -168,7 +168,7 @@ public:
virtual Error resize(int64_t p_length) = 0;
virtual void flush() = 0;
- virtual void store_8(uint8_t p_dest) = 0; ///< store a byte
+ virtual void store_8(uint8_t p_dest); ///< store a byte
virtual void store_16(uint16_t p_dest); ///< store 16 bits uint
virtual void store_32(uint32_t p_dest); ///< store 32 bits uint
virtual void store_64(uint64_t p_dest); ///< store 64 bits uint
@@ -184,7 +184,7 @@ public:
virtual void store_pascal_string(const String &p_string);
virtual String get_pascal_string();
- virtual void store_buffer(const uint8_t *p_src, uint64_t p_length); ///< store an array of bytes
+ virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) = 0; ///< store an array of bytes, needs to be overwritten by children.
void store_buffer(const Vector<uint8_t> &p_buffer);
void store_var(const Variant &p_var, bool p_full_objects = false);
diff --git a/core/io/file_access_compressed.cpp b/core/io/file_access_compressed.cpp
index 0f00bd292c..3602baf8c5 100644
--- a/core/io/file_access_compressed.cpp
+++ b/core/io/file_access_compressed.cpp
@@ -260,38 +260,6 @@ bool FileAccessCompressed::eof_reached() const {
}
}
-uint8_t FileAccessCompressed::get_8() const {
- ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use.");
- ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode.");
-
- if (at_end) {
- read_eof = true;
- return 0;
- }
-
- uint8_t ret = read_ptr[read_pos];
-
- read_pos++;
- if (read_pos >= read_block_size) {
- read_block++;
-
- if (read_block < read_block_count) {
- //read another block of compressed data
- f->get_buffer(comp_buffer.ptrw(), read_blocks[read_block].csize);
- int total = Compression::decompress(buffer.ptrw(), read_blocks.size() == 1 ? read_total : block_size, comp_buffer.ptr(), read_blocks[read_block].csize, cmode);
- ERR_FAIL_COND_V_MSG(total == -1, 0, "Compressed file is corrupt.");
- read_block_size = read_block == read_block_count - 1 ? read_total % block_size : block_size;
- read_pos = 0;
-
- } else {
- read_block--;
- at_end = true;
- }
- }
-
- return ret;
-}
-
uint64_t FileAccessCompressed::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_COND_V_MSG(f.is_null(), -1, "File must be opened before use.");
@@ -341,12 +309,13 @@ void FileAccessCompressed::flush() {
// compressed files keep data in memory till close()
}
-void FileAccessCompressed::store_8(uint8_t p_dest) {
+void FileAccessCompressed::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use.");
ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
- WRITE_FIT(1);
- write_ptr[write_pos++] = p_dest;
+ WRITE_FIT(p_length);
+ memcpy(write_ptr + write_pos, p_src, p_length);
+ write_pos += p_length;
}
bool FileAccessCompressed::file_exists(const String &p_name) {
diff --git a/core/io/file_access_compressed.h b/core/io/file_access_compressed.h
index f706c82f8e..ea9837dd03 100644
--- a/core/io/file_access_compressed.h
+++ b/core/io/file_access_compressed.h
@@ -83,14 +83,13 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
+ virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
diff --git a/core/io/file_access_encrypted.cpp b/core/io/file_access_encrypted.cpp
index b689f5b628..605c5b2f6f 100644
--- a/core/io/file_access_encrypted.cpp
+++ b/core/io/file_access_encrypted.cpp
@@ -206,26 +206,13 @@ bool FileAccessEncrypted::eof_reached() const {
return eofed;
}
-uint8_t FileAccessEncrypted::get_8() const {
- ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode.");
- if (pos >= get_length()) {
- eofed = true;
- return 0;
- }
-
- uint8_t b = data[pos];
- pos++;
- return b;
-}
-
uint64_t FileAccessEncrypted::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_COND_V_MSG(writing, -1, "File has not been opened in read mode.");
uint64_t to_copy = MIN(p_length, get_length() - pos);
- for (uint64_t i = 0; i < to_copy; i++) {
- p_dst[i] = data[pos++];
- }
+ memcpy(p_dst, data.ptr() + pos, to_copy);
+ pos += to_copy;
if (to_copy < p_length) {
eofed = true;
@@ -242,17 +229,12 @@ void FileAccessEncrypted::store_buffer(const uint8_t *p_src, uint64_t p_length)
ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
ERR_FAIL_COND(!p_src && p_length > 0);
- if (pos < get_length()) {
- for (uint64_t i = 0; i < p_length; i++) {
- store_8(p_src[i]);
- }
- } else if (pos == get_length()) {
+ if (pos + p_length >= get_length()) {
data.resize(pos + p_length);
- for (uint64_t i = 0; i < p_length; i++) {
- data.write[pos + i] = p_src[i];
- }
- pos += p_length;
}
+
+ memcpy(data.ptrw() + pos, p_src, p_length);
+ pos += p_length;
}
void FileAccessEncrypted::flush() {
@@ -261,18 +243,6 @@ void FileAccessEncrypted::flush() {
// encrypted files keep data in memory till close()
}
-void FileAccessEncrypted::store_8(uint8_t p_dest) {
- ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode.");
-
- if (pos < get_length()) {
- data.write[pos] = p_dest;
- pos++;
- } else if (pos == get_length()) {
- data.push_back(p_dest);
- pos++;
- }
-}
-
bool FileAccessEncrypted::file_exists(const String &p_name) {
Ref<FileAccess> fa = FileAccess::open(p_name, FileAccess::READ);
if (fa.is_null()) {
diff --git a/core/io/file_access_encrypted.h b/core/io/file_access_encrypted.h
index 42afe49a5e..5f8c803d60 100644
--- a/core/io/file_access_encrypted.h
+++ b/core/io/file_access_encrypted.h
@@ -73,14 +73,12 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
diff --git a/core/io/file_access_memory.cpp b/core/io/file_access_memory.cpp
index 9521a4f666..1541a5ed4a 100644
--- a/core/io/file_access_memory.cpp
+++ b/core/io/file_access_memory.cpp
@@ -122,16 +122,6 @@ bool FileAccessMemory::eof_reached() const {
return pos >= length;
}
-uint8_t FileAccessMemory::get_8() const {
- uint8_t ret = 0;
- if (pos < length) {
- ret = data[pos];
- }
- ++pos;
-
- return ret;
-}
-
uint64_t FileAccessMemory::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_NULL_V(data, -1);
@@ -157,16 +147,12 @@ void FileAccessMemory::flush() {
ERR_FAIL_NULL(data);
}
-void FileAccessMemory::store_8(uint8_t p_byte) {
- ERR_FAIL_NULL(data);
- ERR_FAIL_COND(pos >= length);
- data[pos++] = p_byte;
-}
-
void FileAccessMemory::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND(!p_src && p_length > 0);
+
uint64_t left = length - pos;
uint64_t write = MIN(p_length, left);
+
if (write < p_length) {
WARN_PRINT("Writing less data than requested");
}
diff --git a/core/io/file_access_memory.h b/core/io/file_access_memory.h
index e9fbc26d75..39e1528d97 100644
--- a/core/io/file_access_memory.h
+++ b/core/io/file_access_memory.h
@@ -55,15 +55,12 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
-
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; ///< get an array of bytes
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
- virtual void store_8(uint8_t p_byte) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp
index 02bf0a6039..eec27ce0aa 100644
--- a/core/io/file_access_pack.cpp
+++ b/core/io/file_access_pack.cpp
@@ -313,17 +313,6 @@ bool FileAccessPack::eof_reached() const {
return eof;
}
-uint8_t FileAccessPack::get_8() const {
- ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use.");
- if (pos >= pf.size) {
- eof = true;
- return 0;
- }
-
- pos++;
- return f->get_8();
-}
-
uint64_t FileAccessPack::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V_MSG(f.is_null(), -1, "File must be opened before use.");
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
@@ -366,10 +355,6 @@ void FileAccessPack::flush() {
ERR_FAIL();
}
-void FileAccessPack::store_8(uint8_t p_dest) {
- ERR_FAIL();
-}
-
void FileAccessPack::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL();
}
diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h
index 594ac8f089..595a36bca4 100644
--- a/core/io/file_access_pack.h
+++ b/core/io/file_access_pack.h
@@ -169,8 +169,6 @@ public:
virtual bool eof_reached() const override;
- virtual uint8_t get_8() const override;
-
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual void set_big_endian(bool p_big_endian) override;
@@ -179,8 +177,6 @@ public:
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override;
-
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_name) override;
diff --git a/core/io/file_access_zip.cpp b/core/io/file_access_zip.cpp
index c0d1afc8e1..b33b7b35c3 100644
--- a/core/io/file_access_zip.cpp
+++ b/core/io/file_access_zip.cpp
@@ -291,12 +291,6 @@ bool FileAccessZip::eof_reached() const {
return at_eof;
}
-uint8_t FileAccessZip::get_8() const {
- uint8_t ret = 0;
- get_buffer(&ret, 1);
- return ret;
-}
-
uint64_t FileAccessZip::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_NULL_V(zfile, -1);
@@ -328,7 +322,7 @@ void FileAccessZip::flush() {
ERR_FAIL();
}
-void FileAccessZip::store_8(uint8_t p_dest) {
+void FileAccessZip::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL();
}
diff --git a/core/io/file_access_zip.h b/core/io/file_access_zip.h
index 88b63e93e2..1e11e050df 100644
--- a/core/io/file_access_zip.h
+++ b/core/io/file_access_zip.h
@@ -95,14 +95,13 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
+ virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
diff --git a/core/io/image.cpp b/core/io/image.cpp
index 003646d095..f6065d984b 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -3824,6 +3824,33 @@ void Image::bump_map_to_normal_map(float bump_scale) {
data = result_image;
}
+bool Image::detect_signed(bool p_include_mips) const {
+ ERR_FAIL_COND_V(is_compressed(), false);
+
+ if (format >= Image::FORMAT_RH && format <= Image::FORMAT_RGBAH) {
+ const uint16_t *img_data = reinterpret_cast<const uint16_t *>(data.ptr());
+ const uint64_t img_size = p_include_mips ? (data.size() / 2) : (width * height * get_format_pixel_size(format) / 2);
+
+ for (uint64_t i = 0; i < img_size; i++) {
+ if ((img_data[i] & 0x8000) != 0 && (img_data[i] & 0x7fff) != 0) {
+ return true;
+ }
+ }
+
+ } else if (format >= Image::FORMAT_RF && format <= Image::FORMAT_RGBAF) {
+ const uint32_t *img_data = reinterpret_cast<const uint32_t *>(data.ptr());
+ const uint64_t img_size = p_include_mips ? (data.size() / 4) : (width * height * get_format_pixel_size(format) / 4);
+
+ for (uint64_t i = 0; i < img_size; i++) {
+ if ((img_data[i] & 0x80000000) != 0 && (img_data[i] & 0x7fffffff) != 0) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
void Image::srgb_to_linear() {
if (data.size() == 0) {
return;
diff --git a/core/io/image.h b/core/io/image.h
index 8d09a0b40b..4461ae71a6 100644
--- a/core/io/image.h
+++ b/core/io/image.h
@@ -391,6 +391,8 @@ public:
Ref<Image> get_image_from_mipmap(int p_mipmap) const;
void bump_map_to_normal_map(float bump_scale = 1.0);
+ bool detect_signed(bool p_include_mips = true) const;
+
void blit_rect(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest);
void blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, const Rect2i &p_src_rect, const Point2i &p_dest);
void blend_rect(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest);
diff --git a/core/io/json.cpp b/core/io/json.cpp
index 61051727c1..664ff7857b 100644
--- a/core/io/json.cpp
+++ b/core/io/json.cpp
@@ -588,10 +588,756 @@ void JSON::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_error_line"), &JSON::get_error_line);
ClassDB::bind_method(D_METHOD("get_error_message"), &JSON::get_error_message);
+ ClassDB::bind_static_method("JSON", D_METHOD("to_native", "json", "allow_classes", "allow_scripts"), &JSON::to_native, DEFVAL(false), DEFVAL(false));
+ ClassDB::bind_static_method("JSON", D_METHOD("from_native", "variant", "allow_classes", "allow_scripts"), &JSON::from_native, DEFVAL(false), DEFVAL(false));
+
ADD_PROPERTY(PropertyInfo(Variant::NIL, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), "set_data", "get_data"); // Ensures that it can be serialized as binary.
}
-////
+#define GDTYPE "__gdtype"
+#define VALUES "values"
+#define PASS_ARG p_allow_classes, p_allow_scripts
+
+Variant JSON::from_native(const Variant &p_variant, bool p_allow_classes, bool p_allow_scripts) {
+ switch (p_variant.get_type()) {
+ case Variant::NIL: {
+ Dictionary nil;
+ nil[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return nil;
+ } break;
+ case Variant::BOOL: {
+ return p_variant;
+ } break;
+ case Variant::INT: {
+ return p_variant;
+ } break;
+ case Variant::FLOAT: {
+ return p_variant;
+ } break;
+ case Variant::STRING: {
+ return p_variant;
+ } break;
+ case Variant::VECTOR2: {
+ Dictionary d;
+ Vector2 v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::VECTOR2I: {
+ Dictionary d;
+ Vector2i v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::RECT2: {
+ Dictionary d;
+ Rect2 r = p_variant;
+ d["position"] = from_native(r.position);
+ d["size"] = from_native(r.size);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::RECT2I: {
+ Dictionary d;
+ Rect2i r = p_variant;
+ d["position"] = from_native(r.position);
+ d["size"] = from_native(r.size);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::VECTOR3: {
+ Dictionary d;
+ Vector3 v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::VECTOR3I: {
+ Dictionary d;
+ Vector3i v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::TRANSFORM2D: {
+ Dictionary d;
+ Transform2D t = p_variant;
+ d["x"] = from_native(t[0]);
+ d["y"] = from_native(t[1]);
+ d["origin"] = from_native(t[2]);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::VECTOR4: {
+ Dictionary d;
+ Vector4 v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ values.push_back(v.w);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::VECTOR4I: {
+ Dictionary d;
+ Vector4i v = p_variant;
+ Array values;
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ values.push_back(v.w);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PLANE: {
+ Dictionary d;
+ Plane p = p_variant;
+ d["normal"] = from_native(p.normal);
+ d["d"] = p.d;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::QUATERNION: {
+ Dictionary d;
+ Quaternion q = p_variant;
+ Array values;
+ values.push_back(q.x);
+ values.push_back(q.y);
+ values.push_back(q.z);
+ values.push_back(q.w);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::AABB: {
+ Dictionary d;
+ AABB aabb = p_variant;
+ d["position"] = from_native(aabb.position);
+ d["size"] = from_native(aabb.size);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::BASIS: {
+ Dictionary d;
+ Basis t = p_variant;
+ d["x"] = from_native(t.get_column(0));
+ d["y"] = from_native(t.get_column(1));
+ d["z"] = from_native(t.get_column(2));
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::TRANSFORM3D: {
+ Dictionary d;
+ Transform3D t = p_variant;
+ d["basis"] = from_native(t.basis);
+ d["origin"] = from_native(t.origin);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PROJECTION: {
+ Dictionary d;
+ Projection t = p_variant;
+ d["x"] = from_native(t[0]);
+ d["y"] = from_native(t[1]);
+ d["z"] = from_native(t[2]);
+ d["w"] = from_native(t[3]);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::COLOR: {
+ Dictionary d;
+ Color c = p_variant;
+ Array values;
+ values.push_back(c.r);
+ values.push_back(c.g);
+ values.push_back(c.b);
+ values.push_back(c.a);
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::STRING_NAME: {
+ Dictionary d;
+ d["name"] = String(p_variant);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::NODE_PATH: {
+ Dictionary d;
+ d["path"] = String(p_variant);
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::RID: {
+ Dictionary d;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::OBJECT: {
+ Object *obj = p_variant.get_validated_object();
+
+ if (p_allow_classes && obj) {
+ Dictionary d;
+ List<PropertyInfo> property_list;
+ obj->get_property_list(&property_list);
+
+ d["type"] = obj->get_class();
+ Dictionary p;
+ for (const PropertyInfo &P : property_list) {
+ if (P.usage & PROPERTY_USAGE_STORAGE) {
+ if (P.name == "script" && !p_allow_scripts) {
+ continue;
+ }
+ p[P.name] = from_native(obj->get(P.name), PASS_ARG);
+ }
+ }
+ d["properties"] = p;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } else {
+ Dictionary nil;
+ nil[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return nil;
+ }
+ } break;
+ case Variant::CALLABLE:
+ case Variant::SIGNAL: {
+ Dictionary nil;
+ nil[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return nil;
+ } break;
+ case Variant::DICTIONARY: {
+ Dictionary d = p_variant;
+ List<Variant> keys;
+ d.get_key_list(&keys);
+ bool all_strings = true;
+ for (const Variant &K : keys) {
+ if (K.get_type() != Variant::STRING) {
+ all_strings = false;
+ break;
+ }
+ }
+
+ if (all_strings) {
+ Dictionary ret_dict;
+ for (const Variant &K : keys) {
+ ret_dict[K] = from_native(d[K], PASS_ARG);
+ }
+ return ret_dict;
+ } else {
+ Dictionary ret;
+ Array pairs;
+ for (const Variant &K : keys) {
+ Dictionary pair;
+ pair["key"] = from_native(K, PASS_ARG);
+ pair["value"] = from_native(d[K], PASS_ARG);
+ pairs.push_back(pair);
+ }
+ ret["pairs"] = pairs;
+ ret[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return ret;
+ }
+ } break;
+ case Variant::ARRAY: {
+ Array arr = p_variant;
+ Array ret;
+ for (int i = 0; i < arr.size(); i++) {
+ ret.push_back(from_native(arr[i], PASS_ARG));
+ }
+ return ret;
+ } break;
+ case Variant::PACKED_BYTE_ARRAY: {
+ Dictionary d;
+ PackedByteArray arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_INT32_ARRAY: {
+ Dictionary d;
+ PackedInt32Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+
+ } break;
+ case Variant::PACKED_INT64_ARRAY: {
+ Dictionary d;
+ PackedInt64Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_FLOAT32_ARRAY: {
+ Dictionary d;
+ PackedFloat32Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_FLOAT64_ARRAY: {
+ Dictionary d;
+ PackedFloat64Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_STRING_ARRAY: {
+ Dictionary d;
+ PackedStringArray arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ values.push_back(arr[i]);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_VECTOR2_ARRAY: {
+ Dictionary d;
+ PackedVector2Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ Vector2 v = arr[i];
+ values.push_back(v.x);
+ values.push_back(v.y);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_VECTOR3_ARRAY: {
+ Dictionary d;
+ PackedVector3Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ Vector3 v = arr[i];
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_COLOR_ARRAY: {
+ Dictionary d;
+ PackedColorArray arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ Color v = arr[i];
+ values.push_back(v.r);
+ values.push_back(v.g);
+ values.push_back(v.b);
+ values.push_back(v.a);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ case Variant::PACKED_VECTOR4_ARRAY: {
+ Dictionary d;
+ PackedVector4Array arr = p_variant;
+ Array values;
+ for (int i = 0; i < arr.size(); i++) {
+ Vector4 v = arr[i];
+ values.push_back(v.x);
+ values.push_back(v.y);
+ values.push_back(v.z);
+ values.push_back(v.w);
+ }
+ d[VALUES] = values;
+ d[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return d;
+ } break;
+ default: {
+ ERR_PRINT(vformat("Unhandled conversion from native Variant type '%s' to JSON.", Variant::get_type_name(p_variant.get_type())));
+ } break;
+ }
+
+ Dictionary nil;
+ nil[GDTYPE] = Variant::get_type_name(p_variant.get_type());
+ return nil;
+}
+
+Variant JSON::to_native(const Variant &p_json, bool p_allow_classes, bool p_allow_scripts) {
+ switch (p_json.get_type()) {
+ case Variant::BOOL: {
+ return p_json;
+ } break;
+ case Variant::INT: {
+ return p_json;
+ } break;
+ case Variant::FLOAT: {
+ return p_json;
+ } break;
+ case Variant::STRING: {
+ return p_json;
+ } break;
+ case Variant::STRING_NAME: {
+ return p_json;
+ } break;
+ case Variant::CALLABLE: {
+ return p_json;
+ } break;
+ case Variant::DICTIONARY: {
+ Dictionary d = p_json;
+ if (d.has(GDTYPE)) {
+ // Specific Godot Variant types serialized to JSON.
+ String type = d[GDTYPE];
+ if (type == Variant::get_type_name(Variant::VECTOR2)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 2, Variant());
+ Vector2 v;
+ v.x = values[0];
+ v.y = values[1];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::VECTOR2I)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 2, Variant());
+ Vector2i v;
+ v.x = values[0];
+ v.y = values[1];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::RECT2)) {
+ ERR_FAIL_COND_V(!d.has("position"), Variant());
+ ERR_FAIL_COND_V(!d.has("size"), Variant());
+ Rect2 r;
+ r.position = to_native(d["position"]);
+ r.size = to_native(d["size"]);
+ return r;
+ } else if (type == Variant::get_type_name(Variant::RECT2I)) {
+ ERR_FAIL_COND_V(!d.has("position"), Variant());
+ ERR_FAIL_COND_V(!d.has("size"), Variant());
+ Rect2i r;
+ r.position = to_native(d["position"]);
+ r.size = to_native(d["size"]);
+ return r;
+ } else if (type == Variant::get_type_name(Variant::VECTOR3)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 3, Variant());
+ Vector3 v;
+ v.x = values[0];
+ v.y = values[1];
+ v.z = values[2];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::VECTOR3I)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 3, Variant());
+ Vector3i v;
+ v.x = values[0];
+ v.y = values[1];
+ v.z = values[2];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::TRANSFORM2D)) {
+ ERR_FAIL_COND_V(!d.has("x"), Variant());
+ ERR_FAIL_COND_V(!d.has("y"), Variant());
+ ERR_FAIL_COND_V(!d.has("origin"), Variant());
+ Transform2D t;
+ t[0] = to_native(d["x"]);
+ t[1] = to_native(d["y"]);
+ t[2] = to_native(d["origin"]);
+ return t;
+ } else if (type == Variant::get_type_name(Variant::VECTOR4)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 4, Variant());
+ Vector4 v;
+ v.x = values[0];
+ v.y = values[1];
+ v.z = values[2];
+ v.w = values[3];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::VECTOR4I)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 4, Variant());
+ Vector4i v;
+ v.x = values[0];
+ v.y = values[1];
+ v.z = values[2];
+ v.w = values[3];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::PLANE)) {
+ ERR_FAIL_COND_V(!d.has("normal"), Variant());
+ ERR_FAIL_COND_V(!d.has("d"), Variant());
+ Plane p;
+ p.normal = to_native(d["normal"]);
+ p.d = d["d"];
+ return p;
+ } else if (type == Variant::get_type_name(Variant::QUATERNION)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 4, Variant());
+ Quaternion v;
+ v.x = values[0];
+ v.y = values[1];
+ v.z = values[2];
+ v.w = values[3];
+ return v;
+ } else if (type == Variant::get_type_name(Variant::AABB)) {
+ ERR_FAIL_COND_V(!d.has("position"), Variant());
+ ERR_FAIL_COND_V(!d.has("size"), Variant());
+ AABB r;
+ r.position = to_native(d["position"]);
+ r.size = to_native(d["size"]);
+ return r;
+ } else if (type == Variant::get_type_name(Variant::BASIS)) {
+ ERR_FAIL_COND_V(!d.has("x"), Variant());
+ ERR_FAIL_COND_V(!d.has("y"), Variant());
+ ERR_FAIL_COND_V(!d.has("z"), Variant());
+ Basis b;
+ b.set_column(0, to_native(d["x"]));
+ b.set_column(1, to_native(d["y"]));
+ b.set_column(2, to_native(d["z"]));
+ return b;
+ } else if (type == Variant::get_type_name(Variant::TRANSFORM3D)) {
+ ERR_FAIL_COND_V(!d.has("basis"), Variant());
+ ERR_FAIL_COND_V(!d.has("origin"), Variant());
+ Transform3D t;
+ t.basis = to_native(d["basis"]);
+ t.origin = to_native(d["origin"]);
+ return t;
+ } else if (type == Variant::get_type_name(Variant::PROJECTION)) {
+ ERR_FAIL_COND_V(!d.has("x"), Variant());
+ ERR_FAIL_COND_V(!d.has("y"), Variant());
+ ERR_FAIL_COND_V(!d.has("z"), Variant());
+ ERR_FAIL_COND_V(!d.has("w"), Variant());
+ Projection p;
+ p[0] = to_native(d["x"]);
+ p[1] = to_native(d["y"]);
+ p[2] = to_native(d["z"]);
+ p[3] = to_native(d["w"]);
+ return p;
+ } else if (type == Variant::get_type_name(Variant::COLOR)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() != 4, Variant());
+ Color c;
+ c.r = values[0];
+ c.g = values[1];
+ c.b = values[2];
+ c.a = values[3];
+ return c;
+ } else if (type == Variant::get_type_name(Variant::NODE_PATH)) {
+ ERR_FAIL_COND_V(!d.has("path"), Variant());
+ NodePath np = d["path"];
+ return np;
+ } else if (type == Variant::get_type_name(Variant::STRING_NAME)) {
+ ERR_FAIL_COND_V(!d.has("name"), Variant());
+ StringName s = d["name"];
+ return s;
+ } else if (type == Variant::get_type_name(Variant::OBJECT)) {
+ ERR_FAIL_COND_V(!d.has("type"), Variant());
+ ERR_FAIL_COND_V(!d.has("properties"), Variant());
+
+ ERR_FAIL_COND_V(!p_allow_classes, Variant());
+
+ String obj_type = d["type"];
+ bool is_script = obj_type == "Script" || ClassDB::is_parent_class(obj_type, "Script");
+ ERR_FAIL_COND_V(!p_allow_scripts && is_script, Variant());
+ Object *obj = ClassDB::instantiate(obj_type);
+ ERR_FAIL_NULL_V(obj, Variant());
+
+ Dictionary p = d["properties"];
+
+ List<Variant> keys;
+ p.get_key_list(&keys);
+
+ for (const Variant &K : keys) {
+ String property = K;
+ Variant value = to_native(p[K], PASS_ARG);
+ obj->set(property, value);
+ }
+
+ Variant v(obj);
+
+ return v;
+ } else if (type == Variant::get_type_name(Variant::DICTIONARY)) {
+ ERR_FAIL_COND_V(!d.has("pairs"), Variant());
+ Array pairs = d["pairs"];
+ Dictionary r;
+ for (int i = 0; i < pairs.size(); i++) {
+ Dictionary p = pairs[i];
+ ERR_CONTINUE(!p.has("key"));
+ ERR_CONTINUE(!p.has("value"));
+ r[to_native(p["key"], PASS_ARG)] = to_native(p["value"]);
+ }
+ return r;
+ } else if (type == Variant::get_type_name(Variant::ARRAY)) {
+ ERR_PRINT(vformat("Unexpected Array with '%s' key. Arrays are supported natively.", GDTYPE));
+ } else if (type == Variant::get_type_name(Variant::PACKED_BYTE_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedByteArray pbarr;
+ pbarr.resize(values.size());
+ for (int i = 0; i < pbarr.size(); i++) {
+ pbarr.write[i] = values[i];
+ }
+ return pbarr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_INT32_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedInt32Array arr;
+ arr.resize(values.size());
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = values[i];
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_INT64_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedInt64Array arr;
+ arr.resize(values.size());
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = values[i];
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_FLOAT32_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedFloat32Array arr;
+ arr.resize(values.size());
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = values[i];
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_FLOAT64_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedFloat64Array arr;
+ arr.resize(values.size());
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = values[i];
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_STRING_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ PackedStringArray arr;
+ arr.resize(values.size());
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = values[i];
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR2_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() % 2 != 0, Variant());
+ PackedVector2Array arr;
+ arr.resize(values.size() / 2);
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = Vector2(values[i * 2 + 0], values[i * 2 + 1]);
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR3_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() % 3 != 0, Variant());
+ PackedVector3Array arr;
+ arr.resize(values.size() / 3);
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = Vector3(values[i * 3 + 0], values[i * 3 + 1], values[i * 3 + 2]);
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_COLOR_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() % 4 != 0, Variant());
+ PackedColorArray arr;
+ arr.resize(values.size() / 4);
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = Color(values[i * 4 + 0], values[i * 4 + 1], values[i * 4 + 2], values[i * 4 + 3]);
+ }
+ return arr;
+ } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR4_ARRAY)) {
+ ERR_FAIL_COND_V(!d.has(VALUES), Variant());
+ Array values = d[VALUES];
+ ERR_FAIL_COND_V(values.size() % 4 != 0, Variant());
+ PackedVector4Array arr;
+ arr.resize(values.size() / 4);
+ for (int i = 0; i < arr.size(); i++) {
+ arr.write[i] = Vector4(values[i * 4 + 0], values[i * 4 + 1], values[i * 4 + 2], values[i * 4 + 3]);
+ }
+ return arr;
+ } else {
+ return Variant();
+ }
+ } else {
+ // Regular dictionary with string keys.
+ List<Variant> keys;
+ d.get_key_list(&keys);
+ Dictionary r;
+ for (const Variant &K : keys) {
+ r[K] = to_native(d[K], PASS_ARG);
+ }
+ return r;
+ }
+ } break;
+ case Variant::ARRAY: {
+ Array arr = p_json;
+ Array ret;
+ ret.resize(arr.size());
+ for (int i = 0; i < arr.size(); i++) {
+ ret[i] = to_native(arr[i], PASS_ARG);
+ }
+ return ret;
+ } break;
+ default: {
+ ERR_PRINT(vformat("Unhandled conversion from JSON type '%s' to native Variant type.", Variant::get_type_name(p_json.get_type())));
+ return Variant();
+ }
+ }
+
+ return Variant();
+}
+
+#undef GDTYPE
+#undef VALUES
+#undef PASS_ARG
////////////
diff --git a/core/io/json.h b/core/io/json.h
index 801fa29b4b..67b5e09afa 100644
--- a/core/io/json.h
+++ b/core/io/json.h
@@ -94,6 +94,9 @@ public:
void set_data(const Variant &p_data);
inline int get_error_line() const { return err_line; }
inline String get_error_message() const { return err_str; }
+
+ static Variant from_native(const Variant &p_variant, bool p_allow_classes = false, bool p_allow_scripts = false);
+ static Variant to_native(const Variant &p_json, bool p_allow_classes = false, bool p_allow_scripts = false);
};
class ResourceFormatLoaderJSON : public ResourceFormatLoader {
diff --git a/core/io/logger.cpp b/core/io/logger.cpp
index a24277fe72..26b60f6738 100644
--- a/core/io/logger.cpp
+++ b/core/io/logger.cpp
@@ -84,11 +84,7 @@ void Logger::log_error(const char *p_function, const char *p_file, int p_line, c
err_details = p_code;
}
- if (p_editor_notify) {
- logf_error("%s: %s\n", err_type, err_details);
- } else {
- logf_error("USER %s: %s\n", err_type, err_details);
- }
+ logf_error("%s: %s\n", err_type, err_details);
logf_error(" at: %s (%s:%i)\n", p_function, p_file, p_line);
}
diff --git a/core/io/packed_data_container.h b/core/io/packed_data_container.h
index cc9996101e..f4ffa09022 100644
--- a/core/io/packed_data_container.h
+++ b/core/io/packed_data_container.h
@@ -36,7 +36,7 @@
class PackedDataContainer : public Resource {
GDCLASS(PackedDataContainer, Resource);
- enum {
+ enum : uint32_t {
TYPE_DICT = 0xFFFFFFFF,
TYPE_ARRAY = 0xFFFFFFFE,
};
diff --git a/core/io/resource.cpp b/core/io/resource.cpp
index 598c99c188..ff12dc5851 100644
--- a/core/io/resource.cpp
+++ b/core/io/resource.cpp
@@ -60,32 +60,32 @@ void Resource::set_path(const String &p_path, bool p_take_over) {
p_take_over = false; // Can't take over an empty path
}
- ResourceCache::lock.lock();
+ {
+ MutexLock lock(ResourceCache::lock);
- if (!path_cache.is_empty()) {
- ResourceCache::resources.erase(path_cache);
- }
+ if (!path_cache.is_empty()) {
+ ResourceCache::resources.erase(path_cache);
+ }
- path_cache = "";
+ path_cache = "";
- Ref<Resource> existing = ResourceCache::get_ref(p_path);
+ Ref<Resource> existing = ResourceCache::get_ref(p_path);
- if (existing.is_valid()) {
- if (p_take_over) {
- existing->path_cache = String();
- ResourceCache::resources.erase(p_path);
- } else {
- ResourceCache::lock.unlock();
- ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion).");
+ if (existing.is_valid()) {
+ if (p_take_over) {
+ existing->path_cache = String();
+ ResourceCache::resources.erase(p_path);
+ } else {
+ ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion).");
+ }
}
- }
- path_cache = p_path;
+ path_cache = p_path;
- if (!path_cache.is_empty()) {
- ResourceCache::resources[path_cache] = this;
+ if (!path_cache.is_empty()) {
+ ResourceCache::resources[path_cache] = this;
+ }
}
- ResourceCache::lock.unlock();
_resource_path_changed();
}
@@ -486,15 +486,13 @@ void Resource::set_as_translation_remapped(bool p_remapped) {
return;
}
- ResourceCache::lock.lock();
+ MutexLock lock(ResourceCache::lock);
if (p_remapped) {
ResourceLoader::remapped_list.add(&remapped_list);
} else {
ResourceLoader::remapped_list.remove(&remapped_list);
}
-
- ResourceCache::lock.unlock();
}
#ifdef TOOLS_ENABLED
@@ -564,14 +562,13 @@ Resource::~Resource() {
return;
}
- ResourceCache::lock.lock();
+ MutexLock lock(ResourceCache::lock);
// Only unregister from the cache if this is the actual resource listed there.
// (Other resources can have the same value in `path_cache` if loaded with `CACHE_IGNORE`.)
HashMap<String, Resource *>::Iterator E = ResourceCache::resources.find(path_cache);
if (likely(E && E->value == this)) {
ResourceCache::resources.remove(E);
}
- ResourceCache::lock.unlock();
}
HashMap<String, Resource *> ResourceCache::resources;
@@ -600,18 +597,20 @@ void ResourceCache::clear() {
}
bool ResourceCache::has(const String &p_path) {
- lock.lock();
+ Resource **res = nullptr;
- Resource **res = resources.getptr(p_path);
+ {
+ MutexLock mutex_lock(lock);
- if (res && (*res)->get_reference_count() == 0) {
- // This resource is in the process of being deleted, ignore its existence.
- (*res)->path_cache = String();
- resources.erase(p_path);
- res = nullptr;
- }
+ res = resources.getptr(p_path);
- lock.unlock();
+ if (res && (*res)->get_reference_count() == 0) {
+ // This resource is in the process of being deleted, ignore its existence.
+ (*res)->path_cache = String();
+ resources.erase(p_path);
+ res = nullptr;
+ }
+ }
if (!res) {
return false;
@@ -622,28 +621,27 @@ bool ResourceCache::has(const String &p_path) {
Ref<Resource> ResourceCache::get_ref(const String &p_path) {
Ref<Resource> ref;
- lock.lock();
-
- Resource **res = resources.getptr(p_path);
+ {
+ MutexLock mutex_lock(lock);
+ Resource **res = resources.getptr(p_path);
- if (res) {
- ref = Ref<Resource>(*res);
- }
+ if (res) {
+ ref = Ref<Resource>(*res);
+ }
- if (res && !ref.is_valid()) {
- // This resource is in the process of being deleted, ignore its existence
- (*res)->path_cache = String();
- resources.erase(p_path);
- res = nullptr;
+ if (res && !ref.is_valid()) {
+ // This resource is in the process of being deleted, ignore its existence
+ (*res)->path_cache = String();
+ resources.erase(p_path);
+ res = nullptr;
+ }
}
- lock.unlock();
-
return ref;
}
void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) {
- lock.lock();
+ MutexLock mutex_lock(lock);
LocalVector<String> to_remove;
@@ -663,14 +661,9 @@ void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) {
for (const String &E : to_remove) {
resources.erase(E);
}
-
- lock.unlock();
}
int ResourceCache::get_cached_resource_count() {
- lock.lock();
- int rc = resources.size();
- lock.unlock();
-
- return rc;
+ MutexLock mutex_lock(lock);
+ return resources.size();
}
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index d2c4668d12..7cf101b0de 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -227,28 +227,27 @@ void ResourceFormatLoader::_bind_methods() {
// This should be robust enough to be called redundantly without issues.
void ResourceLoader::LoadToken::clear() {
- thread_load_mutex.lock();
-
WorkerThreadPool::TaskID task_to_await = 0;
- // User-facing tokens shouldn't be deleted until completely claimed.
- DEV_ASSERT(user_rc == 0 && user_path.is_empty());
-
- if (!local_path.is_empty()) { // Empty is used for the special case where the load task is not registered.
- DEV_ASSERT(thread_load_tasks.has(local_path));
- ThreadLoadTask &load_task = thread_load_tasks[local_path];
- if (load_task.task_id && !load_task.awaited) {
- task_to_await = load_task.task_id;
+ {
+ MutexLock thread_load_lock(thread_load_mutex);
+ // User-facing tokens shouldn't be deleted until completely claimed.
+ DEV_ASSERT(user_rc == 0 && user_path.is_empty());
+
+ if (!local_path.is_empty()) { // Empty is used for the special case where the load task is not registered.
+ DEV_ASSERT(thread_load_tasks.has(local_path));
+ ThreadLoadTask &load_task = thread_load_tasks[local_path];
+ if (load_task.task_id && !load_task.awaited) {
+ task_to_await = load_task.task_id;
+ }
+ // Removing a task which is still in progress would be catastrophic.
+ // Tokens must be alive until the task thread function is done.
+ DEV_ASSERT(load_task.status == THREAD_LOAD_FAILED || load_task.status == THREAD_LOAD_LOADED);
+ thread_load_tasks.erase(local_path);
+ local_path.clear();
}
- // Removing a task which is still in progress would be catastrophic.
- // Tokens must be alive until the task thread function is done.
- DEV_ASSERT(load_task.status == THREAD_LOAD_FAILED || load_task.status == THREAD_LOAD_LOADED);
- thread_load_tasks.erase(local_path);
- local_path.clear();
}
- thread_load_mutex.unlock();
-
// If task is unused, await it here, locally, now the token data is consistent.
if (task_to_await) {
PREPARE_FOR_WTP_WAIT
@@ -265,7 +264,7 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
const String &original_path = p_original_path.is_empty() ? p_path : p_original_path;
load_nesting++;
if (load_paths_stack.size()) {
- thread_load_mutex.lock();
+ MutexLock thread_load_lock(thread_load_mutex);
const String &parent_task_path = load_paths_stack.get(load_paths_stack.size() - 1);
HashMap<String, ThreadLoadTask>::Iterator E = thread_load_tasks.find(parent_task_path);
// Avoid double-tracking, for progress reporting, resources that boil down to a remapped path containing the real payload (e.g., imported resources).
@@ -273,7 +272,6 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
if (E && !is_remapped_load) {
E->value.sub_tasks.insert(p_original_path);
}
- thread_load_mutex.unlock();
}
load_paths_stack.push_back(original_path);
@@ -318,13 +316,13 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
void ResourceLoader::_run_load_task(void *p_userdata) {
ThreadLoadTask &load_task = *(ThreadLoadTask *)p_userdata;
- thread_load_mutex.lock();
- if (cleaning_tasks) {
- load_task.status = THREAD_LOAD_FAILED;
- thread_load_mutex.unlock();
- return;
+ {
+ MutexLock thread_load_lock(thread_load_mutex);
+ if (cleaning_tasks) {
+ load_task.status = THREAD_LOAD_FAILED;
+ return;
+ }
}
- thread_load_mutex.unlock();
// Thread-safe either if it's the current thread or a brand new one.
CallQueue *own_mq_override = nullptr;
@@ -343,12 +341,19 @@ void ResourceLoader::_run_load_task(void *p_userdata) {
bool xl_remapped = false;
const String &remapped_path = _path_remap(load_task.local_path, &xl_remapped);
+
+ print_verbose("Loading resource: " + remapped_path);
+
Error load_err = OK;
Ref<Resource> res = _load(remapped_path, remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_err, load_task.use_sub_threads, &load_task.progress);
if (MessageQueue::get_singleton() != MessageQueue::get_main_singleton()) {
MessageQueue::get_singleton()->flush();
}
+ if (res.is_null()) {
+ print_verbose("Failed loading resource: " + remapped_path);
+ }
+
thread_load_mutex.lock();
load_task.resource = res;
@@ -532,6 +537,11 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path,
if (!ignoring_cache && thread_load_tasks.has(local_path)) {
load_token = Ref<LoadToken>(thread_load_tasks[local_path].load_token);
if (load_token.is_valid()) {
+ if (p_for_user) {
+ // Load task exists, with no user tokens at the moment.
+ // Let's "attach" to it.
+ _load_threaded_request_setup_user_token(load_token.ptr(), p_path);
+ }
return load_token;
} else {
// The token is dying (reached 0 on another thread).
@@ -1158,17 +1168,17 @@ String ResourceLoader::path_remap(const String &p_path) {
}
void ResourceLoader::reload_translation_remaps() {
- ResourceCache::lock.lock();
-
List<Resource *> to_reload;
- SelfList<Resource> *E = remapped_list.first();
- while (E) {
- to_reload.push_back(E->self());
- E = E->next();
- }
+ {
+ MutexLock lock(ResourceCache::lock);
+ SelfList<Resource> *E = remapped_list.first();
- ResourceCache::lock.unlock();
+ while (E) {
+ to_reload.push_back(E->self());
+ E = E->next();
+ }
+ }
//now just make sure to not delete any of these resources while changing locale..
while (to_reload.front()) {
diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp
index 4497604947..c53fd3d330 100644
--- a/core/math/a_star.cpp
+++ b/core/math/a_star.cpp
@@ -391,9 +391,9 @@ bool AStar3D::_solve(Point *begin_point, Point *end_point) {
return found_route;
}
-real_t AStar3D::_estimate_cost(int64_t p_from_id, int64_t p_to_id) {
+real_t AStar3D::_estimate_cost(int64_t p_from_id, int64_t p_end_id) {
real_t scost;
- if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_to_id, scost)) {
+ if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_end_id, scost)) {
return scost;
}
@@ -401,11 +401,11 @@ real_t AStar3D::_estimate_cost(int64_t p_from_id, int64_t p_to_id) {
bool from_exists = points.lookup(p_from_id, from_point);
ERR_FAIL_COND_V_MSG(!from_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_from_id));
- Point *to_point = nullptr;
- bool to_exists = points.lookup(p_to_id, to_point);
- ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_to_id));
+ Point *end_point = nullptr;
+ bool end_exists = points.lookup(p_end_id, end_point);
+ ERR_FAIL_COND_V_MSG(!end_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_end_id));
- return from_point->pos.distance_to(to_point->pos);
+ return from_point->pos.distance_to(end_point->pos);
}
real_t AStar3D::_compute_cost(int64_t p_from_id, int64_t p_to_id) {
@@ -579,7 +579,7 @@ void AStar3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id", "allow_partial_path"), &AStar3D::get_point_path, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id", "allow_partial_path"), &AStar3D::get_id_path, DEFVAL(false));
- GDVIRTUAL_BIND(_estimate_cost, "from_id", "to_id")
+ GDVIRTUAL_BIND(_estimate_cost, "from_id", "end_id")
GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id")
}
@@ -675,9 +675,9 @@ Vector2 AStar2D::get_closest_position_in_segment(const Vector2 &p_point) const {
return Vector2(p.x, p.y);
}
-real_t AStar2D::_estimate_cost(int64_t p_from_id, int64_t p_to_id) {
+real_t AStar2D::_estimate_cost(int64_t p_from_id, int64_t p_end_id) {
real_t scost;
- if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_to_id, scost)) {
+ if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_end_id, scost)) {
return scost;
}
@@ -685,11 +685,11 @@ real_t AStar2D::_estimate_cost(int64_t p_from_id, int64_t p_to_id) {
bool from_exists = astar.points.lookup(p_from_id, from_point);
ERR_FAIL_COND_V_MSG(!from_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_from_id));
- AStar3D::Point *to_point = nullptr;
- bool to_exists = astar.points.lookup(p_to_id, to_point);
- ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_to_id));
+ AStar3D::Point *end_point = nullptr;
+ bool to_exists = astar.points.lookup(p_end_id, end_point);
+ ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_end_id));
- return from_point->pos.distance_to(to_point->pos);
+ return from_point->pos.distance_to(end_point->pos);
}
real_t AStar2D::_compute_cost(int64_t p_from_id, int64_t p_to_id) {
@@ -918,6 +918,6 @@ void AStar2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id", "allow_partial_path"), &AStar2D::get_point_path, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id", "allow_partial_path"), &AStar2D::get_id_path, DEFVAL(false));
- GDVIRTUAL_BIND(_estimate_cost, "from_id", "to_id")
+ GDVIRTUAL_BIND(_estimate_cost, "from_id", "end_id")
GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id")
}
diff --git a/core/math/a_star.h b/core/math/a_star.h
index 8e054c4789..143a3bec61 100644
--- a/core/math/a_star.h
+++ b/core/math/a_star.h
@@ -120,7 +120,7 @@ class AStar3D : public RefCounted {
protected:
static void _bind_methods();
- virtual real_t _estimate_cost(int64_t p_from_id, int64_t p_to_id);
+ virtual real_t _estimate_cost(int64_t p_from_id, int64_t p_end_id);
virtual real_t _compute_cost(int64_t p_from_id, int64_t p_to_id);
GDVIRTUAL2RC(real_t, _estimate_cost, int64_t, int64_t)
@@ -176,7 +176,7 @@ class AStar2D : public RefCounted {
protected:
static void _bind_methods();
- virtual real_t _estimate_cost(int64_t p_from_id, int64_t p_to_id);
+ virtual real_t _estimate_cost(int64_t p_from_id, int64_t p_end_id);
virtual real_t _compute_cost(int64_t p_from_id, int64_t p_to_id);
GDVIRTUAL2RC(real_t, _estimate_cost, int64_t, int64_t)
diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp
index 984bb1c9c1..b1d2f82f9d 100644
--- a/core/math/a_star_grid_2d.cpp
+++ b/core/math/a_star_grid_2d.cpp
@@ -535,12 +535,12 @@ bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point) {
return found_route;
}
-real_t AStarGrid2D::_estimate_cost(const Vector2i &p_from_id, const Vector2i &p_to_id) {
+real_t AStarGrid2D::_estimate_cost(const Vector2i &p_from_id, const Vector2i &p_end_id) {
real_t scost;
- if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_to_id, scost)) {
+ if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_end_id, scost)) {
return scost;
}
- return heuristics[default_estimate_heuristic](p_from_id, p_to_id);
+ return heuristics[default_estimate_heuristic](p_from_id, p_end_id);
}
real_t AStarGrid2D::_compute_cost(const Vector2i &p_from_id, const Vector2i &p_to_id) {
@@ -562,6 +562,33 @@ Vector2 AStarGrid2D::get_point_position(const Vector2i &p_id) const {
return _get_point_unchecked(p_id)->pos;
}
+TypedArray<Dictionary> AStarGrid2D::get_point_data_in_region(const Rect2i &p_region) const {
+ ERR_FAIL_COND_V_MSG(dirty, TypedArray<Dictionary>(), "Grid is not initialized. Call the update method.");
+ const Rect2i inter_region = region.intersection(p_region);
+
+ const int32_t start_x = inter_region.position.x - region.position.x;
+ const int32_t start_y = inter_region.position.y - region.position.y;
+ const int32_t end_x = inter_region.get_end().x - region.position.x;
+ const int32_t end_y = inter_region.get_end().y - region.position.y;
+
+ TypedArray<Dictionary> data;
+
+ for (int32_t y = start_y; y < end_y; y++) {
+ for (int32_t x = start_x; x < end_x; x++) {
+ const Point &p = points[y][x];
+
+ Dictionary dict;
+ dict["id"] = p.id;
+ dict["position"] = p.pos;
+ dict["solid"] = p.solid;
+ dict["weight_scale"] = p.weight_scale;
+ data.push_back(dict);
+ }
+ }
+
+ return data;
+}
+
Vector<Vector2> AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vector2i &p_to_id, bool p_allow_partial_path) {
ERR_FAIL_COND_V_MSG(dirty, Vector<Vector2>(), "Grid is not initialized. Call the update method.");
ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), Vector<Vector2>(), vformat("Can't get id path. Point %s out of bounds %s.", p_from_id, region));
@@ -698,10 +725,11 @@ void AStarGrid2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &AStarGrid2D::clear);
ClassDB::bind_method(D_METHOD("get_point_position", "id"), &AStarGrid2D::get_point_position);
+ ClassDB::bind_method(D_METHOD("get_point_data_in_region", "region"), &AStarGrid2D::get_point_data_in_region);
ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id", "allow_partial_path"), &AStarGrid2D::get_point_path, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id", "allow_partial_path"), &AStarGrid2D::get_id_path, DEFVAL(false));
- GDVIRTUAL_BIND(_estimate_cost, "from_id", "to_id")
+ GDVIRTUAL_BIND(_estimate_cost, "from_id", "end_id")
GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id")
ADD_PROPERTY(PropertyInfo(Variant::RECT2I, "region"), "set_region", "get_region");
diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h
index 1a9f6dcc11..c2be0bbf29 100644
--- a/core/math/a_star_grid_2d.h
+++ b/core/math/a_star_grid_2d.h
@@ -151,7 +151,7 @@ private: // Internal routines.
protected:
static void _bind_methods();
- virtual real_t _estimate_cost(const Vector2i &p_from_id, const Vector2i &p_to_id);
+ virtual real_t _estimate_cost(const Vector2i &p_from_id, const Vector2i &p_end_id);
virtual real_t _compute_cost(const Vector2i &p_from_id, const Vector2i &p_to_id);
GDVIRTUAL2RC(real_t, _estimate_cost, Vector2i, Vector2i)
@@ -209,6 +209,7 @@ public:
void clear();
Vector2 get_point_position(const Vector2i &p_id) const;
+ TypedArray<Dictionary> get_point_data_in_region(const Rect2i &p_region) const;
Vector<Vector2> get_point_path(const Vector2i &p_from, const Vector2i &p_to, bool p_allow_partial_path = false);
TypedArray<Vector2i> get_id_path(const Vector2i &p_from, const Vector2i &p_to, bool p_allow_partial_path = false);
};
diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h
index fd53ed28fd..1afc5f4bbb 100644
--- a/core/math/math_funcs.h
+++ b/core/math/math_funcs.h
@@ -105,6 +105,9 @@ public:
static _ALWAYS_INLINE_ double fmod(double p_x, double p_y) { return ::fmod(p_x, p_y); }
static _ALWAYS_INLINE_ float fmod(float p_x, float p_y) { return ::fmodf(p_x, p_y); }
+ static _ALWAYS_INLINE_ double modf(double p_x, double *r_y) { return ::modf(p_x, r_y); }
+ static _ALWAYS_INLINE_ float modf(float p_x, float *r_y) { return ::modff(p_x, r_y); }
+
static _ALWAYS_INLINE_ double floor(double p_x) { return ::floor(p_x); }
static _ALWAYS_INLINE_ float floor(float p_x) { return ::floorf(p_x); }
diff --git a/core/math/random_pcg.cpp b/core/math/random_pcg.cpp
index 55787a0b57..c286a60421 100644
--- a/core/math/random_pcg.cpp
+++ b/core/math/random_pcg.cpp
@@ -60,6 +60,11 @@ int64_t RandomPCG::rand_weighted(const Vector<float> &p_weights) {
}
}
+ for (int64_t i = weights_size - 1; i >= 0; --i) {
+ if (weights[i] > 0) {
+ return i;
+ }
+ }
return -1;
}
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index 5c793a676f..a65411629f 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -181,7 +181,7 @@ public:
return 0;
}
- static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata, bool p_notify_postinitialize) {
+ static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata, GDExtensionBool p_notify_postinitialize) {
ClassDB::ClassInfo *ti = (ClassDB::ClassInfo *)p_class_userdata;
// Find the closest native parent, that isn't a runtime class.
@@ -192,7 +192,7 @@ public:
ERR_FAIL_NULL_V(native_parent->creation_func, nullptr);
// Construct a placeholder.
- Object *obj = native_parent->creation_func(p_notify_postinitialize);
+ Object *obj = native_parent->creation_func(static_cast<bool>(p_notify_postinitialize));
// ClassDB::set_object_extension_instance() won't be called for placeholders.
// We need need to make sure that all the things it would have done (even if
@@ -271,6 +271,22 @@ void ClassDB::get_extensions_class_list(List<StringName> *p_classes) {
p_classes->sort_custom<StringName::AlphCompare>();
}
+
+void ClassDB::get_extension_class_list(const Ref<GDExtension> &p_extension, List<StringName> *p_classes) {
+ OBJTYPE_RLOCK;
+
+ for (const KeyValue<StringName, ClassInfo> &E : classes) {
+ if (E.value.api != API_EXTENSION && E.value.api != API_EDITOR_EXTENSION) {
+ continue;
+ }
+ if (!E.value.gdextension || E.value.gdextension->library != p_extension.ptr()) {
+ continue;
+ }
+ p_classes->push_back(E.key);
+ }
+
+ p_classes->sort_custom<StringName::AlphCompare>();
+}
#endif
void ClassDB::get_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes) {
@@ -1632,14 +1648,16 @@ bool ClassDB::get_property(Object *p_object, const StringName &p_property, Varia
Variant index = psg->index;
const Variant *arg[1] = { &index };
Callable::CallError ce;
- r_value = p_object->callp(psg->getter, arg, 1, ce);
+ const Variant value = p_object->callp(psg->getter, arg, 1, ce);
+ r_value = (ce.error == Callable::CallError::CALL_OK) ? value : Variant();
} else {
Callable::CallError ce;
if (psg->_getptr) {
r_value = psg->_getptr->call(p_object, nullptr, 0, ce);
} else {
- r_value = p_object->callp(psg->getter, nullptr, 0, ce);
+ const Variant value = p_object->callp(psg->getter, nullptr, 0, ce);
+ r_value = (ce.error == Callable::CallError::CALL_OK) ? value : Variant();
}
}
return true;
diff --git a/core/object/class_db.h b/core/object/class_db.h
index d6a95b58e2..620092a6c4 100644
--- a/core/object/class_db.h
+++ b/core/object/class_db.h
@@ -285,6 +285,7 @@ public:
static void get_class_list(List<StringName> *p_classes);
#ifdef TOOLS_ENABLED
static void get_extensions_class_list(List<StringName> *p_classes);
+ static void get_extension_class_list(const Ref<GDExtension> &p_extension, List<StringName> *p_classes);
static ObjectGDExtension *get_placeholder_extension(const StringName &p_class);
#endif
static void get_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes);
diff --git a/core/object/object.cpp b/core/object/object.cpp
index 161b1e3dbe..8b3ab40271 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -746,7 +746,7 @@ Variant Object::callv(const StringName &p_method, const Array &p_args) {
}
Callable::CallError ce;
- Variant ret = callp(p_method, argptrs, p_args.size(), ce);
+ const Variant ret = callp(p_method, argptrs, p_args.size(), ce);
if (ce.error != Callable::CallError::CALL_OK) {
ERR_FAIL_V_MSG(Variant(), "Error calling method from 'callv': " + Variant::get_call_error_text(this, p_method, argptrs, p_args.size(), ce) + ".");
}
@@ -787,7 +787,7 @@ Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_
if (script_instance) {
ret = script_instance->callp(p_method, p_args, p_argcount, r_error);
- //force jumptable
+ // Force jump table.
switch (r_error.error) {
case Callable::CallError::CALL_OK:
return ret;
@@ -997,7 +997,7 @@ void Object::set_meta(const StringName &p_name, const Variant &p_value) {
if (E) {
E->value = p_value;
} else {
- ERR_FAIL_COND_MSG(!p_name.operator String().is_valid_identifier(), "Invalid metadata identifier: '" + p_name + "'.");
+ ERR_FAIL_COND_MSG(!p_name.operator String().is_valid_ascii_identifier(), "Invalid metadata identifier: '" + p_name + "'.");
Variant *V = &metadata.insert(p_name, p_value)->value;
const String &sname = p_name;
@@ -1023,6 +1023,14 @@ void Object::remove_meta(const StringName &p_name) {
set_meta(p_name, Variant());
}
+void Object::merge_meta_from(const Object *p_src) {
+ List<StringName> meta_keys;
+ p_src->get_meta_list(&meta_keys);
+ for (const StringName &key : meta_keys) {
+ set_meta(key, p_src->get_meta(key));
+ }
+}
+
TypedArray<Dictionary> Object::_get_property_list_bind() const {
List<PropertyInfo> lpi;
get_property_list(&lpi);
@@ -1904,7 +1912,7 @@ void Object::set_instance_binding(void *p_token, void *p_binding, const GDExtens
void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindingCallbacks *p_callbacks) {
void *binding = nullptr;
- _instance_binding_mutex.lock();
+ MutexLock instance_binding_lock(_instance_binding_mutex);
for (uint32_t i = 0; i < _instance_binding_count; i++) {
if (_instance_bindings[i].token == p_token) {
binding = _instance_bindings[i].binding;
@@ -1935,14 +1943,12 @@ void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindi
_instance_binding_count++;
}
- _instance_binding_mutex.unlock();
-
return binding;
}
bool Object::has_instance_binding(void *p_token) {
bool found = false;
- _instance_binding_mutex.lock();
+ MutexLock instance_binding_lock(_instance_binding_mutex);
for (uint32_t i = 0; i < _instance_binding_count; i++) {
if (_instance_bindings[i].token == p_token) {
found = true;
@@ -1950,14 +1956,12 @@ bool Object::has_instance_binding(void *p_token) {
}
}
- _instance_binding_mutex.unlock();
-
return found;
}
void Object::free_instance_binding(void *p_token) {
bool found = false;
- _instance_binding_mutex.lock();
+ MutexLock instance_binding_lock(_instance_binding_mutex);
for (uint32_t i = 0; i < _instance_binding_count; i++) {
if (!found && _instance_bindings[i].token == p_token) {
if (_instance_bindings[i].free_callback) {
@@ -1976,7 +1980,6 @@ void Object::free_instance_binding(void *p_token) {
if (found) {
_instance_binding_count--;
}
- _instance_binding_mutex.unlock();
}
#ifdef TOOLS_ENABLED
@@ -2294,7 +2297,7 @@ void ObjectDB::cleanup() {
// Ensure calling the native classes because if a leaked instance has a script
// that overrides any of those methods, it'd not be OK to call them at this point,
// now the scripting languages have already been terminated.
- MethodBind *node_get_name = ClassDB::get_method("Node", "get_name");
+ MethodBind *node_get_path = ClassDB::get_method("Node", "get_path");
MethodBind *resource_get_path = ClassDB::get_method("Resource", "get_path");
Callable::CallError call_error;
@@ -2304,7 +2307,7 @@ void ObjectDB::cleanup() {
String extra_info;
if (obj->is_class("Node")) {
- extra_info = " - Node name: " + String(node_get_name->call(obj, nullptr, 0, call_error));
+ extra_info = " - Node path: " + String(node_get_path->call(obj, nullptr, 0, call_error));
}
if (obj->is_class("Resource")) {
extra_info = " - Resource path: " + String(resource_get_path->call(obj, nullptr, 0, call_error));
diff --git a/core/object/object.h b/core/object/object.h
index 7307b7ede0..bc3f663baf 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -387,18 +387,6 @@ struct ObjectGDExtension {
* much alone defines the object model.
*/
-#define REVERSE_GET_PROPERTY_LIST \
-public: \
- _FORCE_INLINE_ bool _is_gpl_reversed() const { return true; }; \
- \
-private:
-
-#define UNREVERSE_GET_PROPERTY_LIST \
-public: \
- _FORCE_INLINE_ bool _is_gpl_reversed() const { return false; }; \
- \
-private:
-
#define GDCLASS(m_class, m_inherits) \
private: \
void operator=(const m_class &p_rval) {} \
@@ -510,15 +498,10 @@ protected:
m_inherits::_get_property_listv(p_list, p_reversed); \
} \
p_list->push_back(PropertyInfo(Variant::NIL, get_class_static(), PROPERTY_HINT_NONE, get_class_static(), PROPERTY_USAGE_CATEGORY)); \
- if (!_is_gpl_reversed()) { \
- ::ClassDB::get_property_list(#m_class, p_list, true, this); \
- } \
+ ::ClassDB::get_property_list(#m_class, p_list, true, this); \
if (m_class::_get_get_property_list() != m_inherits::_get_get_property_list()) { \
_get_property_list(p_list); \
} \
- if (_is_gpl_reversed()) { \
- ::ClassDB::get_property_list(#m_class, p_list, true, this); \
- } \
if (p_reversed) { \
m_inherits::_get_property_listv(p_list, p_reversed); \
} \
@@ -684,7 +667,7 @@ protected:
_FORCE_INLINE_ bool _instance_binding_reference(bool p_reference) {
bool can_die = true;
if (_instance_bindings) {
- _instance_binding_mutex.lock();
+ MutexLock instance_binding_lock(_instance_binding_mutex);
for (uint32_t i = 0; i < _instance_binding_count; i++) {
if (_instance_bindings[i].reference_callback) {
if (!_instance_bindings[i].reference_callback(_instance_bindings[i].token, _instance_bindings[i].binding, p_reference)) {
@@ -692,7 +675,6 @@ protected:
}
}
}
- _instance_binding_mutex.unlock();
}
return can_die;
}
@@ -799,8 +781,6 @@ public:
return &ptr;
}
- bool _is_gpl_reversed() const { return false; }
-
void detach_from_objectdb();
_FORCE_INLINE_ ObjectID get_instance_id() const { return _instance_id; }
@@ -887,7 +867,8 @@ public:
argptrs[i] = &args[i];
}
Callable::CallError cerr;
- return callp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args), cerr);
+ const Variant ret = callp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args), cerr);
+ return (cerr.error == Callable::CallError::CALL_OK) ? ret : Variant();
}
void notification(int p_notification, bool p_reversed = false);
@@ -914,6 +895,7 @@ public:
MTVIRTUAL void remove_meta(const StringName &p_name);
MTVIRTUAL Variant get_meta(const StringName &p_name, const Variant &p_default = Variant()) const;
MTVIRTUAL void get_meta_list(List<StringName> *p_list) const;
+ MTVIRTUAL void merge_meta_from(const Object *p_src);
#ifdef TOOLS_ENABLED
void set_edited(bool p_edited);
diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp
index cdc56e5ec5..57e5195137 100644
--- a/core/object/script_language.cpp
+++ b/core/object/script_language.cpp
@@ -704,6 +704,19 @@ bool PlaceHolderScriptInstance::has_method(const StringName &p_method) const {
return false;
}
+Variant PlaceHolderScriptInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
+#if TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ return String("Attempt to call a method on a placeholder instance. Check if the script is in tool mode.");
+ } else {
+ return String("Attempt to call a method on a placeholder instance. Probably a bug, please report.");
+ }
+#else
+ return Variant();
+#endif // TOOLS_ENABLED
+}
+
void PlaceHolderScriptInstance::update(const List<PropertyInfo> &p_properties, const HashMap<StringName, Variant> &p_values) {
HashSet<StringName> new_values;
for (const PropertyInfo &E : p_properties) {
diff --git a/core/object/script_language.h b/core/object/script_language.h
index 59a43a7b29..e38c344ae5 100644
--- a/core/object/script_language.h
+++ b/core/object/script_language.h
@@ -454,10 +454,7 @@ public:
return 0;
}
- virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override {
- r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
- return Variant();
- }
+ virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
virtual void notification(int p_notification, bool p_reversed = false) override {}
virtual Ref<Script> get_script() const override { return script; }
diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp
index 7fd43c4094..fe7bbd474c 100644
--- a/core/object/worker_thread_pool.cpp
+++ b/core/object/worker_thread_pool.cpp
@@ -127,9 +127,8 @@ void WorkerThreadPool::_process_task(Task *p_task) {
if (finished_users == max_users) {
// Get rid of the group, because nobody else is using it.
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
group_allocator.free(p_task->group);
- task_mutex.unlock();
}
// For groups, tasks get rid of themselves.
@@ -349,17 +348,13 @@ WorkerThreadPool::TaskID WorkerThreadPool::add_task(const Callable &p_action, bo
}
bool WorkerThreadPool::is_task_completed(TaskID p_task_id) const {
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
const Task *const *taskp = tasks.getptr(p_task_id);
if (!taskp) {
- task_mutex.unlock();
ERR_FAIL_V_MSG(false, "Invalid Task ID"); // Invalid task
}
- bool completed = (*taskp)->completed;
- task_mutex.unlock();
-
- return completed;
+ return (*taskp)->completed;
}
Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) {
@@ -461,7 +456,10 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T
p_caller_pool_thread->signaled = false;
if (IS_WAIT_OVER) {
- p_caller_pool_thread->yield_is_over = false;
+ if (unlikely(p_task == ThreadData::YIELDING)) {
+ p_caller_pool_thread->yield_is_over = false;
+ }
+
if (!exit_threads && was_signaled) {
// This thread was awaken for some additional reason, but it's about to exit.
// Let's find out what may be pending and forward the requests.
@@ -519,10 +517,9 @@ void WorkerThreadPool::yield() {
}
void WorkerThreadPool::notify_yield_over(TaskID p_task_id) {
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
Task **taskp = tasks.getptr(p_task_id);
if (!taskp) {
- task_mutex.unlock();
ERR_FAIL_MSG("Invalid Task ID.");
}
Task *task = *taskp;
@@ -531,7 +528,6 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) {
// This avoids a race condition where a task is created and yield-over called before it's processed.
task->pending_notify_yield_over = true;
}
- task_mutex.unlock();
return;
}
@@ -539,8 +535,6 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) {
td.yield_is_over = true;
td.signaled = true;
td.cond_var.notify_one();
-
- task_mutex.unlock();
}
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) {
@@ -598,26 +592,20 @@ WorkerThreadPool::GroupID WorkerThreadPool::add_group_task(const Callable &p_act
}
uint32_t WorkerThreadPool::get_group_processed_element_count(GroupID p_group) const {
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
const Group *const *groupp = groups.getptr(p_group);
if (!groupp) {
- task_mutex.unlock();
ERR_FAIL_V_MSG(0, "Invalid Group ID");
}
- uint32_t elements = (*groupp)->completed_index.get();
- task_mutex.unlock();
- return elements;
+ return (*groupp)->completed_index.get();
}
bool WorkerThreadPool::is_group_task_completed(GroupID p_group) const {
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
const Group *const *groupp = groups.getptr(p_group);
if (!groupp) {
- task_mutex.unlock();
ERR_FAIL_V_MSG(false, "Invalid Group ID");
}
- bool completed = (*groupp)->completed.is_set();
- task_mutex.unlock();
- return completed;
+ return (*groupp)->completed.is_set();
}
void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) {
@@ -641,15 +629,13 @@ void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) {
if (finished_users == max_users) {
// All tasks using this group are gone (finished before the group), so clear the group too.
- task_mutex.lock();
+ MutexLock task_lock(task_mutex);
group_allocator.free(group);
- task_mutex.unlock();
}
}
- task_mutex.lock(); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread.
+ MutexLock task_lock(task_mutex); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread.
groups.erase(p_group);
- task_mutex.unlock();
#endif
}
@@ -701,6 +687,8 @@ void WorkerThreadPool::init(int p_thread_count, float p_low_priority_task_ratio)
max_low_priority_threads = CLAMP(p_thread_count * p_low_priority_task_ratio, 1, p_thread_count - 1);
+ print_verbose(vformat("WorkerThreadPool: %d threads, %d max low-priority.", p_thread_count, max_low_priority_threads));
+
threads.resize(p_thread_count);
for (uint32_t i = 0; i < threads.size(); i++) {
diff --git a/core/os/os.h b/core/os/os.h
index 91e0ce9379..be8820eba9 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -137,6 +137,7 @@ public:
int get_display_driver_id() const { return _display_driver_id; }
virtual Vector<String> get_video_adapter_driver_info() const = 0;
+ virtual bool get_user_prefers_integrated_gpu() const { return false; }
void print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, Logger::ErrorType p_type = Logger::ERR_ERROR);
void print(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;
diff --git a/core/os/pool_allocator.cpp b/core/os/pool_allocator.cpp
deleted file mode 100644
index 9a993cd14f..0000000000
--- a/core/os/pool_allocator.cpp
+++ /dev/null
@@ -1,588 +0,0 @@
-/**************************************************************************/
-/* pool_allocator.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 "pool_allocator.h"
-
-#include "core/error/error_macros.h"
-#include "core/os/memory.h"
-#include "core/os/os.h"
-#include "core/string/print_string.h"
-
-#define COMPACT_CHUNK(m_entry, m_to_pos) \
- if constexpr (true) { \
- void *_dst = &((unsigned char *)pool)[m_to_pos]; \
- void *_src = &((unsigned char *)pool)[(m_entry).pos]; \
- memmove(_dst, _src, aligned((m_entry).len)); \
- (m_entry).pos = m_to_pos; \
- } else \
- ((void)0)
-
-void PoolAllocator::mt_lock() const {
-}
-
-void PoolAllocator::mt_unlock() const {
-}
-
-bool PoolAllocator::get_free_entry(EntryArrayPos *p_pos) {
- if (entry_count == entry_max) {
- return false;
- }
-
- for (int i = 0; i < entry_max; i++) {
- if (entry_array[i].len == 0) {
- *p_pos = i;
- return true;
- }
- }
-
- ERR_PRINT("Out of memory Chunks!");
-
- return false; //
-}
-
-/**
- * Find a hole
- * @param p_pos The hole is behind the block pointed by this variable upon return. if pos==entry_count, then allocate at end
- * @param p_for_size hole size
- * @return false if hole found, true if no hole found
- */
-bool PoolAllocator::find_hole(EntryArrayPos *p_pos, int p_for_size) {
- /* position where previous entry ends. Defaults to zero (begin of pool) */
-
- int prev_entry_end_pos = 0;
-
- for (int i = 0; i < entry_count; i++) {
- Entry &entry = entry_array[entry_indices[i]];
-
- /* determine hole size to previous entry */
-
- int hole_size = entry.pos - prev_entry_end_pos;
-
- /* determine if what we want fits in that hole */
- if (hole_size >= p_for_size) {
- *p_pos = i;
- return true;
- }
-
- /* prepare for next one */
- prev_entry_end_pos = entry_end(entry);
- }
-
- /* No holes between entries, check at the end..*/
-
- if ((pool_size - prev_entry_end_pos) >= p_for_size) {
- *p_pos = entry_count;
- return true;
- }
-
- return false;
-}
-
-void PoolAllocator::compact(int p_up_to) {
- uint32_t prev_entry_end_pos = 0;
-
- if (p_up_to < 0) {
- p_up_to = entry_count;
- }
- for (int i = 0; i < p_up_to; i++) {
- Entry &entry = entry_array[entry_indices[i]];
-
- /* determine hole size to previous entry */
-
- int hole_size = entry.pos - prev_entry_end_pos;
-
- /* if we can compact, do it */
- if (hole_size > 0 && !entry.lock) {
- COMPACT_CHUNK(entry, prev_entry_end_pos);
- }
-
- /* prepare for next one */
- prev_entry_end_pos = entry_end(entry);
- }
-}
-
-void PoolAllocator::compact_up(int p_from) {
- uint32_t next_entry_end_pos = pool_size; // - static_area_size;
-
- for (int i = entry_count - 1; i >= p_from; i--) {
- Entry &entry = entry_array[entry_indices[i]];
-
- /* determine hole size for next entry */
-
- int hole_size = next_entry_end_pos - (entry.pos + aligned(entry.len));
-
- /* if we can compact, do it */
- if (hole_size > 0 && !entry.lock) {
- COMPACT_CHUNK(entry, (next_entry_end_pos - aligned(entry.len)));
- }
-
- /* prepare for next one */
- next_entry_end_pos = entry.pos;
- }
-}
-
-bool PoolAllocator::find_entry_index(EntryIndicesPos *p_map_pos, const Entry *p_entry) {
- EntryArrayPos entry_pos = entry_max;
-
- for (int i = 0; i < entry_count; i++) {
- if (&entry_array[entry_indices[i]] == p_entry) {
- entry_pos = i;
- break;
- }
- }
-
- if (entry_pos == entry_max) {
- return false;
- }
-
- *p_map_pos = entry_pos;
- return true;
-}
-
-PoolAllocator::ID PoolAllocator::alloc(int p_size) {
- ERR_FAIL_COND_V(p_size < 1, POOL_ALLOCATOR_INVALID_ID);
- ERR_FAIL_COND_V(p_size > free_mem, POOL_ALLOCATOR_INVALID_ID);
-
- mt_lock();
-
- if (entry_count == entry_max) {
- mt_unlock();
- ERR_PRINT("entry_count==entry_max");
- return POOL_ALLOCATOR_INVALID_ID;
- }
-
- int size_to_alloc = aligned(p_size);
-
- EntryIndicesPos new_entry_indices_pos;
-
- if (!find_hole(&new_entry_indices_pos, size_to_alloc)) {
- /* No hole could be found, try compacting mem */
- compact();
- /* Then search again */
-
- if (!find_hole(&new_entry_indices_pos, size_to_alloc)) {
- mt_unlock();
- ERR_FAIL_V_MSG(POOL_ALLOCATOR_INVALID_ID, "Memory can't be compacted further.");
- }
- }
-
- EntryArrayPos new_entry_array_pos;
-
- bool found_free_entry = get_free_entry(&new_entry_array_pos);
-
- if (!found_free_entry) {
- mt_unlock();
- ERR_FAIL_V_MSG(POOL_ALLOCATOR_INVALID_ID, "No free entry found in PoolAllocator.");
- }
-
- /* move all entry indices up, make room for this one */
- for (int i = entry_count; i > new_entry_indices_pos; i--) {
- entry_indices[i] = entry_indices[i - 1];
- }
-
- entry_indices[new_entry_indices_pos] = new_entry_array_pos;
-
- entry_count++;
-
- Entry &entry = entry_array[entry_indices[new_entry_indices_pos]];
-
- entry.len = p_size;
- entry.pos = (new_entry_indices_pos == 0) ? 0 : entry_end(entry_array[entry_indices[new_entry_indices_pos - 1]]); //alloc either at beginning or end of previous
- entry.lock = 0;
- entry.check = (check_count++) & CHECK_MASK;
- free_mem -= size_to_alloc;
- if (free_mem < free_mem_peak) {
- free_mem_peak = free_mem;
- }
-
- ID retval = (entry_indices[new_entry_indices_pos] << CHECK_BITS) | entry.check;
- mt_unlock();
-
- //ERR_FAIL_COND_V( (uintptr_t)get(retval)%align != 0, retval );
-
- return retval;
-}
-
-PoolAllocator::Entry *PoolAllocator::get_entry(ID p_mem) {
- unsigned int check = p_mem & CHECK_MASK;
- int entry = p_mem >> CHECK_BITS;
- ERR_FAIL_INDEX_V(entry, entry_max, nullptr);
- ERR_FAIL_COND_V(entry_array[entry].check != check, nullptr);
- ERR_FAIL_COND_V(entry_array[entry].len == 0, nullptr);
-
- return &entry_array[entry];
-}
-
-const PoolAllocator::Entry *PoolAllocator::get_entry(ID p_mem) const {
- unsigned int check = p_mem & CHECK_MASK;
- int entry = p_mem >> CHECK_BITS;
- ERR_FAIL_INDEX_V(entry, entry_max, nullptr);
- ERR_FAIL_COND_V(entry_array[entry].check != check, nullptr);
- ERR_FAIL_COND_V(entry_array[entry].len == 0, nullptr);
-
- return &entry_array[entry];
-}
-
-void PoolAllocator::free(ID p_mem) {
- mt_lock();
- Entry *e = get_entry(p_mem);
- if (!e) {
- mt_unlock();
- ERR_PRINT("!e");
- return;
- }
- if (e->lock) {
- mt_unlock();
- ERR_PRINT("e->lock");
- return;
- }
-
- EntryIndicesPos entry_indices_pos;
-
- bool index_found = find_entry_index(&entry_indices_pos, e);
- if (!index_found) {
- mt_unlock();
- ERR_FAIL_COND(!index_found);
- }
-
- for (int i = entry_indices_pos; i < (entry_count - 1); i++) {
- entry_indices[i] = entry_indices[i + 1];
- }
-
- entry_count--;
- free_mem += aligned(e->len);
- e->clear();
- mt_unlock();
-}
-
-int PoolAllocator::get_size(ID p_mem) const {
- int size;
- mt_lock();
-
- const Entry *e = get_entry(p_mem);
- if (!e) {
- mt_unlock();
- ERR_PRINT("!e");
- return 0;
- }
-
- size = e->len;
-
- mt_unlock();
-
- return size;
-}
-
-Error PoolAllocator::resize(ID p_mem, int p_new_size) {
- mt_lock();
- Entry *e = get_entry(p_mem);
-
- if (!e) {
- mt_unlock();
- ERR_FAIL_NULL_V(e, ERR_INVALID_PARAMETER);
- }
-
- if (needs_locking && e->lock) {
- mt_unlock();
- ERR_FAIL_COND_V(e->lock, ERR_ALREADY_IN_USE);
- }
-
- uint32_t alloc_size = aligned(p_new_size);
-
- if ((uint32_t)aligned(e->len) == alloc_size) {
- e->len = p_new_size;
- mt_unlock();
- return OK;
- } else if (e->len > (uint32_t)p_new_size) {
- free_mem += aligned(e->len);
- free_mem -= alloc_size;
- e->len = p_new_size;
- mt_unlock();
- return OK;
- }
-
- //p_new_size = align(p_new_size)
- int _free = free_mem; // - static_area_size;
-
- if (uint32_t(_free + aligned(e->len)) < alloc_size) {
- mt_unlock();
- ERR_FAIL_V(ERR_OUT_OF_MEMORY);
- }
-
- EntryIndicesPos entry_indices_pos;
-
- bool index_found = find_entry_index(&entry_indices_pos, e);
-
- if (!index_found) {
- mt_unlock();
- ERR_FAIL_COND_V(!index_found, ERR_BUG);
- }
-
- //no need to move stuff around, it fits before the next block
- uint32_t next_pos;
- if (entry_indices_pos + 1 == entry_count) {
- next_pos = pool_size; // - static_area_size;
- } else {
- next_pos = entry_array[entry_indices[entry_indices_pos + 1]].pos;
- }
-
- if ((next_pos - e->pos) > alloc_size) {
- free_mem += aligned(e->len);
- e->len = p_new_size;
- free_mem -= alloc_size;
- mt_unlock();
- return OK;
- }
- //it doesn't fit, compact around BEFORE current index (make room behind)
-
- compact(entry_indices_pos + 1);
-
- if ((next_pos - e->pos) > alloc_size) {
- //now fits! hooray!
- free_mem += aligned(e->len);
- e->len = p_new_size;
- free_mem -= alloc_size;
- mt_unlock();
- if (free_mem < free_mem_peak) {
- free_mem_peak = free_mem;
- }
- return OK;
- }
-
- //STILL doesn't fit, compact around AFTER current index (make room after)
-
- compact_up(entry_indices_pos + 1);
-
- if ((entry_array[entry_indices[entry_indices_pos + 1]].pos - e->pos) > alloc_size) {
- //now fits! hooray!
- free_mem += aligned(e->len);
- e->len = p_new_size;
- free_mem -= alloc_size;
- mt_unlock();
- if (free_mem < free_mem_peak) {
- free_mem_peak = free_mem;
- }
- return OK;
- }
-
- mt_unlock();
- ERR_FAIL_V(ERR_OUT_OF_MEMORY);
-}
-
-Error PoolAllocator::lock(ID p_mem) {
- if (!needs_locking) {
- return OK;
- }
- mt_lock();
- Entry *e = get_entry(p_mem);
- if (!e) {
- mt_unlock();
- ERR_PRINT("!e");
- return ERR_INVALID_PARAMETER;
- }
- e->lock++;
- mt_unlock();
- return OK;
-}
-
-bool PoolAllocator::is_locked(ID p_mem) const {
- if (!needs_locking) {
- return false;
- }
-
- mt_lock();
- const Entry *e = const_cast<PoolAllocator *>(this)->get_entry(p_mem);
- if (!e) {
- mt_unlock();
- ERR_PRINT("!e");
- return false;
- }
- bool locked = e->lock;
- mt_unlock();
- return locked;
-}
-
-const void *PoolAllocator::get(ID p_mem) const {
- if (!needs_locking) {
- const Entry *e = get_entry(p_mem);
- ERR_FAIL_NULL_V(e, nullptr);
- return &pool[e->pos];
- }
-
- mt_lock();
- const Entry *e = get_entry(p_mem);
-
- if (!e) {
- mt_unlock();
- ERR_FAIL_NULL_V(e, nullptr);
- }
- if (e->lock == 0) {
- mt_unlock();
- ERR_PRINT("e->lock == 0");
- return nullptr;
- }
-
- if ((int)e->pos >= pool_size) {
- mt_unlock();
- ERR_PRINT("e->pos<0 || e->pos>=pool_size");
- return nullptr;
- }
- const void *ptr = &pool[e->pos];
-
- mt_unlock();
-
- return ptr;
-}
-
-void *PoolAllocator::get(ID p_mem) {
- if (!needs_locking) {
- Entry *e = get_entry(p_mem);
- ERR_FAIL_NULL_V(e, nullptr);
- return &pool[e->pos];
- }
-
- mt_lock();
- Entry *e = get_entry(p_mem);
-
- if (!e) {
- mt_unlock();
- ERR_FAIL_NULL_V(e, nullptr);
- }
- if (e->lock == 0) {
- mt_unlock();
- ERR_PRINT("e->lock == 0");
- return nullptr;
- }
-
- if ((int)e->pos >= pool_size) {
- mt_unlock();
- ERR_PRINT("e->pos<0 || e->pos>=pool_size");
- return nullptr;
- }
- void *ptr = &pool[e->pos];
-
- mt_unlock();
-
- return ptr;
-}
-
-void PoolAllocator::unlock(ID p_mem) {
- if (!needs_locking) {
- return;
- }
- mt_lock();
- Entry *e = get_entry(p_mem);
- if (!e) {
- mt_unlock();
- ERR_FAIL_NULL(e);
- }
- if (e->lock == 0) {
- mt_unlock();
- ERR_PRINT("e->lock == 0");
- return;
- }
- e->lock--;
- mt_unlock();
-}
-
-int PoolAllocator::get_used_mem() const {
- return pool_size - free_mem;
-}
-
-int PoolAllocator::get_free_peak() {
- return free_mem_peak;
-}
-
-int PoolAllocator::get_free_mem() {
- return free_mem;
-}
-
-void PoolAllocator::create_pool(void *p_mem, int p_size, int p_max_entries) {
- pool = (uint8_t *)p_mem;
- pool_size = p_size;
-
- entry_array = memnew_arr(Entry, p_max_entries);
- entry_indices = memnew_arr(int, p_max_entries);
- entry_max = p_max_entries;
- entry_count = 0;
-
- free_mem = p_size;
- free_mem_peak = p_size;
-
- check_count = 0;
-}
-
-PoolAllocator::PoolAllocator(int p_size, bool p_needs_locking, int p_max_entries) {
- mem_ptr = memalloc(p_size);
- ERR_FAIL_NULL(mem_ptr);
- align = 1;
- create_pool(mem_ptr, p_size, p_max_entries);
- needs_locking = p_needs_locking;
-}
-
-PoolAllocator::PoolAllocator(void *p_mem, int p_size, int p_align, bool p_needs_locking, int p_max_entries) {
- if (p_align > 1) {
- uint8_t *mem8 = (uint8_t *)p_mem;
- uint64_t ofs = (uint64_t)mem8;
- if (ofs % p_align) {
- int dif = p_align - (ofs % p_align);
- mem8 += p_align - (ofs % p_align);
- p_size -= dif;
- p_mem = (void *)mem8;
- }
- }
-
- create_pool(p_mem, p_size, p_max_entries);
- needs_locking = p_needs_locking;
- align = p_align;
- mem_ptr = nullptr;
-}
-
-PoolAllocator::PoolAllocator(int p_align, int p_size, bool p_needs_locking, int p_max_entries) {
- ERR_FAIL_COND(p_align < 1);
- mem_ptr = Memory::alloc_static(p_size + p_align, true);
- uint8_t *mem8 = (uint8_t *)mem_ptr;
- uint64_t ofs = (uint64_t)mem8;
- if (ofs % p_align) {
- mem8 += p_align - (ofs % p_align);
- }
- create_pool(mem8, p_size, p_max_entries);
- needs_locking = p_needs_locking;
- align = p_align;
-}
-
-PoolAllocator::~PoolAllocator() {
- if (mem_ptr) {
- memfree(mem_ptr);
- }
-
- memdelete_arr(entry_array);
- memdelete_arr(entry_indices);
-}
diff --git a/core/os/pool_allocator.h b/core/os/pool_allocator.h
deleted file mode 100644
index be8b6e061b..0000000000
--- a/core/os/pool_allocator.h
+++ /dev/null
@@ -1,148 +0,0 @@
-/**************************************************************************/
-/* pool_allocator.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 POOL_ALLOCATOR_H
-#define POOL_ALLOCATOR_H
-
-#include "core/typedefs.h"
-
-/**
- * Generic Pool Allocator.
- * This is a generic memory pool allocator, with locking, compacting and alignment. (@TODO alignment)
- * It used as a standard way to manage allocation in a specific region of memory, such as texture memory,
- * audio sample memory, or just any kind of memory overall.
- * (@TODO) abstraction should be greater, because in many platforms, you need to manage a nonreachable memory.
- */
-
-enum {
- POOL_ALLOCATOR_INVALID_ID = -1 ///< default invalid value. use INVALID_ID( id ) to test
-};
-
-class PoolAllocator {
-public:
- typedef int ID;
-
-private:
- enum {
- CHECK_BITS = 8,
- CHECK_LEN = (1 << CHECK_BITS),
- CHECK_MASK = CHECK_LEN - 1
-
- };
-
- struct Entry {
- unsigned int pos = 0;
- unsigned int len = 0;
- unsigned int lock = 0;
- unsigned int check = 0;
-
- inline void clear() {
- pos = 0;
- len = 0;
- lock = 0;
- check = 0;
- }
- Entry() {}
- };
-
- typedef int EntryArrayPos;
- typedef int EntryIndicesPos;
-
- Entry *entry_array = nullptr;
- int *entry_indices = nullptr;
- int entry_max = 0;
- int entry_count = 0;
-
- uint8_t *pool = nullptr;
- void *mem_ptr = nullptr;
- int pool_size = 0;
-
- int free_mem = 0;
- int free_mem_peak = 0;
-
- unsigned int check_count = 0;
- int align = 1;
-
- bool needs_locking = false;
-
- inline int entry_end(const Entry &p_entry) const {
- return p_entry.pos + aligned(p_entry.len);
- }
- inline int aligned(int p_size) const {
- int rem = p_size % align;
- if (rem) {
- p_size += align - rem;
- }
-
- return p_size;
- }
-
- void compact(int p_up_to = -1);
- void compact_up(int p_from = 0);
- bool get_free_entry(EntryArrayPos *p_pos);
- bool find_hole(EntryArrayPos *p_pos, int p_for_size);
- bool find_entry_index(EntryIndicesPos *p_map_pos, const Entry *p_entry);
- Entry *get_entry(ID p_mem);
- const Entry *get_entry(ID p_mem) const;
-
- void create_pool(void *p_mem, int p_size, int p_max_entries);
-
-protected:
- virtual void mt_lock() const; ///< Reimplement for custom mt locking
- virtual void mt_unlock() const; ///< Reimplement for custom mt locking
-
-public:
- enum {
- DEFAULT_MAX_ALLOCS = 4096,
- };
-
- ID alloc(int p_size); ///< Alloc memory, get an ID on success, POOL_ALOCATOR_INVALID_ID on failure
- void free(ID p_mem); ///< Free allocated memory
- Error resize(ID p_mem, int p_new_size); ///< resize a memory chunk
- int get_size(ID p_mem) const;
-
- int get_free_mem(); ///< get free memory
- int get_used_mem() const;
- int get_free_peak(); ///< get free memory
-
- Error lock(ID p_mem); //@todo move this out
- void *get(ID p_mem);
- const void *get(ID p_mem) const;
- void unlock(ID p_mem);
- bool is_locked(ID p_mem) const;
-
- PoolAllocator(int p_size, bool p_needs_locking = false, int p_max_entries = DEFAULT_MAX_ALLOCS);
- PoolAllocator(void *p_mem, int p_size, int p_align = 1, bool p_needs_locking = false, int p_max_entries = DEFAULT_MAX_ALLOCS);
- PoolAllocator(int p_align, int p_size, bool p_needs_locking = false, int p_max_entries = DEFAULT_MAX_ALLOCS);
-
- virtual ~PoolAllocator();
-};
-
-#endif // POOL_ALLOCATOR_H
diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp
index 5d59d65f92..28077fc8c5 100644
--- a/core/string/string_name.cpp
+++ b/core/string/string_name.cpp
@@ -191,11 +191,10 @@ StringName::StringName(const StringName &p_name) {
}
void StringName::assign_static_unique_class_name(StringName *ptr, const char *p_name) {
- mutex.lock();
+ MutexLock lock(mutex);
if (*ptr == StringName()) {
*ptr = StringName(p_name, true);
}
- mutex.unlock();
}
StringName::StringName(const char *p_name, bool p_static) {
diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp
index 6e784881d0..4ac79ad10a 100644
--- a/core/string/translation_server.cpp
+++ b/core/string/translation_server.cpp
@@ -284,6 +284,11 @@ String TranslationServer::_standardize_locale(const String &p_locale, bool p_add
}
int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const {
+ if (p_locale_a == p_locale_b) {
+ // Exact match.
+ return 10;
+ }
+
String locale_a = _standardize_locale(p_locale_a, true);
String locale_b = _standardize_locale(p_locale_b, true);
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 303998ab50..2683addd4b 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -4624,7 +4624,7 @@ bool String::is_absolute_path() const {
}
}
-String String::validate_identifier() const {
+String String::validate_ascii_identifier() const {
if (is_empty()) {
return "_"; // Empty string is not a valid identifier;
}
@@ -4647,7 +4647,7 @@ String String::validate_identifier() const {
return result;
}
-bool String::is_valid_identifier() const {
+bool String::is_valid_ascii_identifier() const {
int len = length();
if (len == 0) {
@@ -4669,6 +4669,26 @@ bool String::is_valid_identifier() const {
return true;
}
+bool String::is_valid_unicode_identifier() const {
+ const char32_t *str = ptr();
+ int len = length();
+
+ if (len == 0) {
+ return false; // Empty string.
+ }
+
+ if (!is_unicode_identifier_start(str[0])) {
+ return false;
+ }
+
+ for (int i = 1; i < len; i++) {
+ if (!is_unicode_identifier_continue(str[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
bool String::is_valid_string() const {
int l = length();
const char32_t *src = get_data();
diff --git a/core/string/ustring.h b/core/string/ustring.h
index 9df2d56e80..11f15031f9 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -459,10 +459,11 @@ public:
// node functions
static String get_invalid_node_name_characters(bool p_allow_internal = false);
String validate_node_name() const;
- String validate_identifier() const;
+ String validate_ascii_identifier() const;
String validate_filename() const;
- bool is_valid_identifier() const;
+ bool is_valid_ascii_identifier() const;
+ bool is_valid_unicode_identifier() const;
bool is_valid_int() const;
bool is_valid_float() const;
bool is_valid_hex_number(bool p_with_prefix) const;
@@ -470,6 +471,9 @@ public:
bool is_valid_ip_address() const;
bool is_valid_filename() const;
+ // Use `is_valid_ascii_identifier()` instead. Kept for compatibility.
+ bool is_valid_identifier() const { return is_valid_ascii_identifier(); }
+
/**
* The constructors must not depend on other overloads
*/
diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h
index 61b90e2a26..fa49767d46 100644
--- a/core/variant/binder_common.h
+++ b/core/variant/binder_common.h
@@ -214,11 +214,11 @@ struct VariantCaster<char32_t> {
template <>
struct PtrToArg<char32_t> {
_FORCE_INLINE_ static char32_t convert(const void *p_ptr) {
- return char32_t(*reinterpret_cast<const int *>(p_ptr));
+ return char32_t(*reinterpret_cast<const int64_t *>(p_ptr));
}
typedef int64_t EncodeT;
_FORCE_INLINE_ static void encode(char32_t p_val, const void *p_ptr) {
- *(int *)p_ptr = p_val;
+ *(int64_t *)p_ptr = p_val;
}
};
diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp
index 667aae879c..9dff5c1e91 100644
--- a/core/variant/callable.cpp
+++ b/core/variant/callable.cpp
@@ -112,7 +112,7 @@ Error Callable::rpcp(int p_id, const Variant **p_arguments, int p_argcount, Call
argptrs[i + 2] = p_arguments[i];
}
- CallError tmp;
+ CallError tmp; // TODO: Check `tmp`?
Error err = (Error)obj->callp(SNAME("rpc_id"), argptrs, argcount, tmp).operator int64_t();
r_call_error.error = Callable::CallError::CALL_OK;
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index c1ef31c784..186643b024 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -1072,13 +1072,6 @@ bool Variant::is_null() const {
}
}
-bool Variant::initialize_ref(Object *p_object) {
- RefCounted *ref_counted = const_cast<RefCounted *>(static_cast<const RefCounted *>(p_object));
- if (!ref_counted->init_ref()) {
- return false;
- }
- return true;
-}
void Variant::reference(const Variant &p_variant) {
switch (type) {
case NIL:
@@ -2120,7 +2113,7 @@ Variant::operator ::RID() const {
}
#endif
Callable::CallError ce;
- Variant ret = _get_obj().obj->callp(CoreStringName(get_rid), nullptr, 0, ce);
+ const Variant ret = _get_obj().obj->callp(CoreStringName(get_rid), nullptr, 0, ce);
if (ce.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::RID) {
return ret;
}
diff --git a/core/variant/variant.h b/core/variant/variant.h
index 1cb3580c01..d4e4b330cd 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -254,7 +254,6 @@ private:
} _data alignas(8);
void reference(const Variant &p_variant);
- static bool initialize_ref(Object *p_object);
void _clear_internal();
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index 695ad23bf5..83f1f981b3 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -1724,6 +1724,8 @@ static void _register_variant_builtin_methods_string() {
bind_string_method(validate_node_name, sarray(), varray());
bind_string_method(validate_filename, sarray(), varray());
+ bind_string_method(is_valid_ascii_identifier, sarray(), varray());
+ bind_string_method(is_valid_unicode_identifier, sarray(), varray());
bind_string_method(is_valid_identifier, sarray(), varray());
bind_string_method(is_valid_int, sarray(), varray());
bind_string_method(is_valid_float, sarray(), varray());
diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h
index c52ab6917b..58a45c0a1f 100644
--- a/core/variant/variant_internal.h
+++ b/core/variant/variant_internal.h
@@ -125,10 +125,6 @@ public:
}
}
- _FORCE_INLINE_ static bool initialize_ref(Object *object) {
- return Variant::initialize_ref(object);
- }
-
// Atomic types.
_FORCE_INLINE_ static bool *get_bool(Variant *v) { return &v->_data._bool; }
_FORCE_INLINE_ static const bool *get_bool(const Variant *v) { return &v->_data._bool; }
diff --git a/doc/classes/AStar2D.xml b/doc/classes/AStar2D.xml
index cfb7d00861..f3a1f6b985 100644
--- a/doc/classes/AStar2D.xml
+++ b/doc/classes/AStar2D.xml
@@ -22,7 +22,7 @@
<method name="_estimate_cost" qualifiers="virtual const">
<return type="float" />
<param index="0" name="from_id" type="int" />
- <param index="1" name="to_id" type="int" />
+ <param index="1" name="end_id" type="int" />
<description>
Called when estimating the cost between a point and the path's ending point.
Note that this function is hidden in the default [AStar2D] class.
diff --git a/doc/classes/AStar3D.xml b/doc/classes/AStar3D.xml
index 4448698c32..dad77cc7a8 100644
--- a/doc/classes/AStar3D.xml
+++ b/doc/classes/AStar3D.xml
@@ -51,7 +51,7 @@
<method name="_estimate_cost" qualifiers="virtual const">
<return type="float" />
<param index="0" name="from_id" type="int" />
- <param index="1" name="to_id" type="int" />
+ <param index="1" name="end_id" type="int" />
<description>
Called when estimating the cost between a point and the path's ending point.
Note that this function is hidden in the default [AStar3D] class.
diff --git a/doc/classes/AStarGrid2D.xml b/doc/classes/AStarGrid2D.xml
index 5ccc0c8f2a..2ee61bd939 100644
--- a/doc/classes/AStarGrid2D.xml
+++ b/doc/classes/AStarGrid2D.xml
@@ -41,7 +41,7 @@
<method name="_estimate_cost" qualifiers="virtual const">
<return type="float" />
<param index="0" name="from_id" type="Vector2i" />
- <param index="1" name="to_id" type="Vector2i" />
+ <param index="1" name="end_id" type="Vector2i" />
<description>
Called when estimating the cost between a point and the path's ending point.
Note that this function is hidden in the default [AStarGrid2D] class.
@@ -81,6 +81,13 @@
If there is no valid path to the target, and [param allow_partial_path] is [code]true[/code], returns a path to the point closest to the target that can be reached.
</description>
</method>
+ <method name="get_point_data_in_region" qualifiers="const">
+ <return type="Dictionary[]" />
+ <param index="0" name="region" type="Rect2i" />
+ <description>
+ Returns an array of dictionaries with point data ([code]id[/code]: [Vector2i], [code]position[/code]: [Vector2], [code]solid[/code]: [bool], [code]weight_scale[/code]: [float]) within a [param region].
+ </description>
+ </method>
<method name="get_point_path">
<return type="PackedVector2Array" />
<param index="0" name="from_id" type="Vector2i" />
diff --git a/doc/classes/AnimationMixer.xml b/doc/classes/AnimationMixer.xml
index dc1bee4336..d762ffa5a6 100644
--- a/doc/classes/AnimationMixer.xml
+++ b/doc/classes/AnimationMixer.xml
@@ -376,7 +376,19 @@
</constant>
<constant name="ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS" value="2" enum="AnimationCallbackModeDiscrete">
Always treat the [constant Animation.UPDATE_DISCRETE] track value as [constant Animation.UPDATE_CONTINUOUS] with [constant Animation.INTERPOLATION_NEAREST]. This is the default behavior for [AnimationTree].
- If a value track has non-numeric type key values, it is internally converted to use [constant ANIMATION_CALLBACK_MODE_DISCRETE_RECESSIVE] with [constant Animation.UPDATE_DISCRETE].
+ If a value track has un-interpolatable type key values, it is internally converted to use [constant ANIMATION_CALLBACK_MODE_DISCRETE_RECESSIVE] with [constant Animation.UPDATE_DISCRETE].
+ Un-interpolatable type list:
+ - [constant @GlobalScope.TYPE_NIL]
+ - [constant @GlobalScope.TYPE_NODE_PATH]
+ - [constant @GlobalScope.TYPE_RID]
+ - [constant @GlobalScope.TYPE_OBJECT]
+ - [constant @GlobalScope.TYPE_CALLABLE]
+ - [constant @GlobalScope.TYPE_SIGNAL]
+ - [constant @GlobalScope.TYPE_DICTIONARY]
+ - [constant @GlobalScope.TYPE_PACKED_BYTE_ARRAY]
+ [constant @GlobalScope.TYPE_BOOL] and [constant @GlobalScope.TYPE_INT] are treated as [constant @GlobalScope.TYPE_FLOAT] during blending and rounded when the result is retrieved.
+ It is same for arrays and vectors with them such as [constant @GlobalScope.TYPE_PACKED_INT32_ARRAY] or [constant @GlobalScope.TYPE_VECTOR2I], they are treated as [constant @GlobalScope.TYPE_PACKED_FLOAT32_ARRAY] or [constant @GlobalScope.TYPE_VECTOR2]. Also note that for arrays, the size is also interpolated.
+ [constant @GlobalScope.TYPE_STRING] and [constant @GlobalScope.TYPE_STRING_NAME] are interpolated between character codes and lengths, but note that there is a difference in algorithm between interpolation between keys and interpolation by blending.
</constant>
</constants>
</class>
diff --git a/doc/classes/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml
index eecbb05540..93680de21e 100644
--- a/doc/classes/AudioStreamPlayer.xml
+++ b/doc/classes/AudioStreamPlayer.xml
@@ -77,7 +77,7 @@
<member name="playback_type" type="int" setter="set_playback_type" getter="get_playback_type" enum="AudioServer.PlaybackType" default="0" experimental="">
The playback type of the stream player. If set other than to the default value, it will force that playback type.
</member>
- <member name="playing" type="bool" setter="_set_playing" getter="is_playing" default="false">
+ <member name="playing" type="bool" setter="set_playing" getter="is_playing" default="false">
If [code]true[/code], this node is playing sounds. Setting this property has the same effect as [method play] and [method stop].
</member>
<member name="stream" type="AudioStream" setter="set_stream" getter="get_stream">
diff --git a/doc/classes/AudioStreamPlayer2D.xml b/doc/classes/AudioStreamPlayer2D.xml
index a3206ba1d6..71d2e1f0db 100644
--- a/doc/classes/AudioStreamPlayer2D.xml
+++ b/doc/classes/AudioStreamPlayer2D.xml
@@ -81,7 +81,7 @@
<member name="playback_type" type="int" setter="set_playback_type" getter="get_playback_type" enum="AudioServer.PlaybackType" default="0" experimental="">
The playback type of the stream player. If set other than to the default value, it will force that playback type.
</member>
- <member name="playing" type="bool" setter="_set_playing" getter="is_playing" default="false">
+ <member name="playing" type="bool" setter="set_playing" getter="is_playing" default="false">
If [code]true[/code], audio is playing or is queued to be played (see [method play]).
</member>
<member name="stream" type="AudioStream" setter="set_stream" getter="get_stream">
diff --git a/doc/classes/AudioStreamPlayer3D.xml b/doc/classes/AudioStreamPlayer3D.xml
index bf02caffb4..a14c53bbfb 100644
--- a/doc/classes/AudioStreamPlayer3D.xml
+++ b/doc/classes/AudioStreamPlayer3D.xml
@@ -102,7 +102,7 @@
<member name="playback_type" type="int" setter="set_playback_type" getter="get_playback_type" enum="AudioServer.PlaybackType" default="0" experimental="">
The playback type of the stream player. If set other than to the default value, it will force that playback type.
</member>
- <member name="playing" type="bool" setter="_set_playing" getter="is_playing" default="false">
+ <member name="playing" type="bool" setter="set_playing" getter="is_playing" default="false">
If [code]true[/code], audio is playing or is queued to be played (see [method play]).
</member>
<member name="stream" type="AudioStream" setter="set_stream" getter="get_stream">
diff --git a/doc/classes/AudioStreamWAV.xml b/doc/classes/AudioStreamWAV.xml
index 8a28514ed6..8d882deaee 100644
--- a/doc/classes/AudioStreamWAV.xml
+++ b/doc/classes/AudioStreamWAV.xml
@@ -15,7 +15,7 @@
<return type="int" enum="Error" />
<param index="0" name="path" type="String" />
<description>
- Saves the AudioStreamWAV as a WAV file to [param path]. Samples with IMA ADPCM or QOA formats can't be saved.
+ Saves the AudioStreamWAV as a WAV file to [param path]. Samples with IMA ADPCM or Quite OK Audio formats can't be saved.
[b]Note:[/b] A [code].wav[/code] extension is automatically appended to [param path] if it is missing.
</description>
</method>
@@ -23,19 +23,20 @@
<members>
<member name="data" type="PackedByteArray" setter="set_data" getter="get_data" default="PackedByteArray()">
Contains the audio data in bytes.
- [b]Note:[/b] This property expects signed PCM8 data. To convert unsigned PCM8 to signed PCM8, subtract 128 from each byte.
+ [b]Note:[/b] If [member format] is set to [constant FORMAT_8_BITS], this property expects signed 8-bit PCM data. To convert from unsigned 8-bit PCM, subtract 128 from each byte.
+ [b]Note:[/b] If [member format] is set to [constant FORMAT_QOA], this property expects data from a full QOA file.
</member>
<member name="format" type="int" setter="set_format" getter="get_format" enum="AudioStreamWAV.Format" default="0">
Audio format. See [enum Format] constants for values.
</member>
<member name="loop_begin" type="int" setter="set_loop_begin" getter="get_loop_begin" default="0">
- The loop start point (in number of samples, relative to the beginning of the stream). This information will be imported automatically from the WAV file if present.
+ The loop start point (in number of samples, relative to the beginning of the stream).
</member>
<member name="loop_end" type="int" setter="set_loop_end" getter="get_loop_end" default="0">
- The loop end point (in number of samples, relative to the beginning of the stream). This information will be imported automatically from the WAV file if present.
+ The loop end point (in number of samples, relative to the beginning of the stream).
</member>
<member name="loop_mode" type="int" setter="set_loop_mode" getter="get_loop_mode" enum="AudioStreamWAV.LoopMode" default="0">
- The loop mode. This information will be imported automatically from the WAV file if present. See [enum LoopMode] constants for values.
+ The loop mode. See [enum LoopMode] constants for values.
</member>
<member name="mix_rate" type="int" setter="set_mix_rate" getter="get_mix_rate" default="44100">
The sample rate for mixing this audio. Higher values require more storage space, but result in better quality.
@@ -48,16 +49,16 @@
</members>
<constants>
<constant name="FORMAT_8_BITS" value="0" enum="Format">
- 8-bit audio codec.
+ 8-bit PCM audio codec.
</constant>
<constant name="FORMAT_16_BITS" value="1" enum="Format">
- 16-bit audio codec.
+ 16-bit PCM audio codec.
</constant>
<constant name="FORMAT_IMA_ADPCM" value="2" enum="Format">
- Audio is compressed using IMA ADPCM.
+ Audio is lossily compressed as IMA ADPCM.
</constant>
<constant name="FORMAT_QOA" value="3" enum="Format">
- Audio is compressed as QOA ([url=https://qoaformat.org/]Quite OK Audio[/url]).
+ Audio is lossily compressed as [url=https://qoaformat.org/]Quite OK Audio[/url].
</constant>
<constant name="LOOP_DISABLED" value="0" enum="LoopMode">
Audio does not loop.
diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml
index 27726ff8a2..d7770f2cd5 100644
--- a/doc/classes/CPUParticles3D.xml
+++ b/doc/classes/CPUParticles3D.xml
@@ -168,6 +168,10 @@
<member name="emission_ring_axis" type="Vector3" setter="set_emission_ring_axis" getter="get_emission_ring_axis">
The axis of the ring when using the emitter [constant EMISSION_SHAPE_RING].
</member>
+ <member name="emission_ring_cone_angle" type="float" setter="set_emission_ring_cone_angle" getter="get_emission_ring_cone_angle">
+ The angle of the cone when using the emitter [constant EMISSION_SHAPE_RING]. The default angle of 90 degrees results in a ring, while an angle of 0 degrees results in a cone. Intermediate values will result in a ring where one end is larger than the other.
+ [b]Note:[/b] Depending on [member emission_ring_height], the angle may be clamped if the ring's end is reached to form a perfect cone.
+ </member>
<member name="emission_ring_height" type="float" setter="set_emission_ring_height" getter="get_emission_ring_height">
The height of the ring when using the emitter [constant EMISSION_SHAPE_RING].
</member>
diff --git a/doc/classes/CodeEdit.xml b/doc/classes/CodeEdit.xml
index d455799c29..136b8e5fc5 100644
--- a/doc/classes/CodeEdit.xml
+++ b/doc/classes/CodeEdit.xml
@@ -721,6 +721,9 @@
<theme_item name="can_fold_code_region" data_type="icon" type="Texture2D">
Sets a custom [Texture2D] to draw in the line folding gutter when a code region can be folded.
</theme_item>
+ <theme_item name="completion_color_bg" data_type="icon" type="Texture2D">
+ Background panel for the color preview box in autocompletion (visible when the color is translucent).
+ </theme_item>
<theme_item name="executing_line" data_type="icon" type="Texture2D">
Icon to draw in the executing gutter for executing lines.
</theme_item>
diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml
index a0c76a3ad6..927ab9ae0e 100644
--- a/doc/classes/Control.xml
+++ b/doc/classes/Control.xml
@@ -1333,13 +1333,14 @@
Tells the parent [Container] to align the node with its end, either the bottom or the right edge. It is mutually exclusive with [constant SIZE_FILL] and other shrink size flags, but can be used with [constant SIZE_EXPAND] in some containers. Use with [member size_flags_horizontal] and [member size_flags_vertical].
</constant>
<constant name="MOUSE_FILTER_STOP" value="0" enum="MouseFilter">
- The control will receive mouse movement input events and mouse button input events if clicked on through [method _gui_input]. And the control will receive the [signal mouse_entered] and [signal mouse_exited] signals. These events are automatically marked as handled, and they will not propagate further to other controls. This also results in blocking signals in other controls.
+ The control will receive mouse movement input events and mouse button input events if clicked on through [method _gui_input]. The control will also receive the [signal mouse_entered] and [signal mouse_exited] signals. These events are automatically marked as handled, and they will not propagate further to other controls. This also results in blocking signals in other controls.
</constant>
<constant name="MOUSE_FILTER_PASS" value="1" enum="MouseFilter">
- The control will receive mouse movement input events and mouse button input events if clicked on through [method _gui_input]. And the control will receive the [signal mouse_entered] and [signal mouse_exited] signals. If this control does not handle the event, the parent control (if any) will be considered, and so on until there is no more parent control to potentially handle it. This also allows signals to fire in other controls. If no control handled it, the event will be passed to [method Node._shortcut_input] for further processing.
+ The control will receive mouse movement input events and mouse button input events if clicked on through [method _gui_input]. The control will also receive the [signal mouse_entered] and [signal mouse_exited] signals.
+ If this control does not handle the event, the event will propagate up to its parent control if it has one. The event is bubbled up the node hierarchy until it reaches a non-[CanvasItem], a control with [constant MOUSE_FILTER_STOP], or a [CanvasItem] with [member CanvasItem.top_level] enabled. This will allow signals to fire in all controls it reaches. If no control handled it, the event will be passed to [method Node._shortcut_input] for further processing.
</constant>
<constant name="MOUSE_FILTER_IGNORE" value="2" enum="MouseFilter">
- The control will not receive mouse movement input events and mouse button input events if clicked on through [method _gui_input]. The control will also not receive the [signal mouse_entered] nor [signal mouse_exited] signals. This will not block other controls from receiving these events or firing the signals. Ignored events will not be handled automatically.
+ The control will not receive any mouse movement input events nor mouse button input events through [method _gui_input]. The control will also not receive the [signal mouse_entered] nor [signal mouse_exited] signals. This will not block other controls from receiving these events or firing the signals. Ignored events will not be handled automatically. If a child has [constant MOUSE_FILTER_PASS] and an event was passed to this control, the event will further propagate up to the control's parent.
[b]Note:[/b] If the control has received [signal mouse_entered] but not [signal mouse_exited], changing the [member mouse_filter] to [constant MOUSE_FILTER_IGNORE] will cause [signal mouse_exited] to be emitted.
</constant>
<constant name="GROW_DIRECTION_BEGIN" value="0" enum="GrowDirection">
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 0bed5288bd..79064a88ba 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -1772,7 +1772,7 @@
<param index="0" name="window_id" type="int" />
<param index="1" name="parent_window_id" type="int" />
<description>
- Sets window transient parent. Transient window is will be destroyed with its transient parent and will return focus to their parent when closed. The transient window is displayed on top of a non-exclusive full-screen parent window. Transient windows can't enter full-screen mode.
+ Sets window transient parent. Transient window will be destroyed with its transient parent and will return focus to their parent when closed. The transient window is displayed on top of a non-exclusive full-screen parent window. Transient windows can't enter full-screen mode.
[b]Note:[/b] It's recommended to change this value using [member Window.transient] instead.
[b]Note:[/b] The behavior might be different depending on the platform.
</description>
diff --git a/doc/classes/EditorExportPlatform.xml b/doc/classes/EditorExportPlatform.xml
index 0e5de79b25..d4084e84a0 100644
--- a/doc/classes/EditorExportPlatform.xml
+++ b/doc/classes/EditorExportPlatform.xml
@@ -11,11 +11,217 @@
<link title="Console support in Godot">$DOCS_URL/tutorials/platform/consoles.html</link>
</tutorials>
<methods>
+ <method name="add_message">
+ <return type="void" />
+ <param index="0" name="type" type="int" enum="EditorExportPlatform.ExportMessageType" />
+ <param index="1" name="category" type="String" />
+ <param index="2" name="message" type="String" />
+ <description>
+ Adds a message to the export log that will be displayed when exporting ends.
+ </description>
+ </method>
+ <method name="clear_messages">
+ <return type="void" />
+ <description>
+ Clears the export log.
+ </description>
+ </method>
+ <method name="create_preset">
+ <return type="EditorExportPreset" />
+ <description>
+ Create a new preset for this platform.
+ </description>
+ </method>
+ <method name="export_pack">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" />
+ <description>
+ Creates a PCK archive at [param path] for the specified [param preset].
+ </description>
+ </method>
+ <method name="export_project">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" />
+ <description>
+ Creates a full project at [param path] for the specified [param preset].
+ </description>
+ </method>
+ <method name="export_project_files">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="save_cb" type="Callable" />
+ <param index="3" name="shared_cb" type="Callable" default="Callable()" />
+ <description>
+ Exports project files for the specified preset. This method can be used to implement custom export format, other than PCK and ZIP. One of the callbacks is called for each exported file.
+ [param save_cb] is called for all exported files and have the following arguments: [code]file_path: String[/code], [code]file_data: PackedByteArray[/code], [code]file_index: int[/code], [code]file_count: int[/code], [code]encryption_include_filters: PackedStringArray[/code], [code]encryption_exclude_filters: PackedStringArray[/code], [code]encryption_key: PackedByteArray[/code].
+ [param shared_cb] is called for exported native shared/static libraries and have the following arguments: [code]file_path: String[/code], [code]tags: PackedStringArray[/code], [code]target_folder: String[/code].
+ [b]Note:[/b] [code]file_index[/code] and [code]file_count[/code] are intended for progress tracking only and aren't necesserely unique and precise.
+ </description>
+ </method>
+ <method name="export_zip">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" />
+ <description>
+ Create a ZIP archive at [param path] for the specified [param preset].
+ </description>
+ </method>
+ <method name="find_export_template" qualifiers="const">
+ <return type="Dictionary" />
+ <param index="0" name="template_file_name" type="String" />
+ <description>
+ Locates export template for the platform, and returns [Dictionary] with the following keys: [code]path: String[/code] and [code]error: String[/code]. This method is provided for convenience and custom export platforms aren't required to use it or keep export templates stored in the same way official templates are.
+ </description>
+ </method>
+ <method name="gen_export_flags">
+ <return type="PackedStringArray" />
+ <param index="0" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ Generates array of command line arguments for the default export templates for the debug flags and editor settings.
+ </description>
+ </method>
+ <method name="get_current_presets" qualifiers="const">
+ <return type="Array" />
+ <description>
+ Returns array of [EditorExportPreset]s for this platform.
+ </description>
+ </method>
+ <method name="get_forced_export_files" qualifiers="static">
+ <return type="PackedStringArray" />
+ <description>
+ Returns array of core file names that always should be exported regardless of preset config.
+ </description>
+ </method>
+ <method name="get_message_category" qualifiers="const">
+ <return type="String" />
+ <param index="0" name="index" type="int" />
+ <description>
+ Returns message category, for the message with [param index].
+ </description>
+ </method>
+ <method name="get_message_count" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns number of messages in the export log.
+ </description>
+ </method>
+ <method name="get_message_text" qualifiers="const">
+ <return type="String" />
+ <param index="0" name="index" type="int" />
+ <description>
+ Returns message text, for the message with [param index].
+ </description>
+ </method>
+ <method name="get_message_type" qualifiers="const">
+ <return type="int" enum="EditorExportPlatform.ExportMessageType" />
+ <param index="0" name="index" type="int" />
+ <description>
+ Returns message type, for the message with [param index].
+ </description>
+ </method>
<method name="get_os_name" qualifiers="const">
<return type="String" />
<description>
Returns the name of the export operating system handled by this [EditorExportPlatform] class, as a friendly string. Possible return values are [code]Windows[/code], [code]Linux[/code], [code]macOS[/code], [code]Android[/code], [code]iOS[/code], and [code]Web[/code].
</description>
</method>
+ <method name="get_worst_message_type" qualifiers="const">
+ <return type="int" enum="EditorExportPlatform.ExportMessageType" />
+ <description>
+ Returns most severe message type currently present in the export log.
+ </description>
+ </method>
+ <method name="save_pack">
+ <return type="Dictionary" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="embed" type="bool" default="false" />
+ <description>
+ Saves PCK archive and returns [Dictionary] with the following keys: [code]result: Error[/code], [code]so_files: Array[/code] (array of the shared/static objects which contains dictionaries with the following keys: [code]path: String[/code], [code]tags: PackedStringArray[/code], and [code]target_folder: String[/code]).
+ If [param embed] is [code]true[/code], PCK content is appended to the end of [param path] file and return [Dictionary] additionally include following keys: [code]embedded_start: int[/code] (embedded PCK offset) and [code]embedded_size: int[/code] (embedded PCK size).
+ </description>
+ </method>
+ <method name="save_zip">
+ <return type="Dictionary" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <description>
+ Saves ZIP archive and returns [Dictionary] with the following keys: [code]result: Error[/code], [code]so_files: Array[/code] (array of the shared/static objects which contains dictionaries with the following keys: [code]path: String[/code], [code]tags: PackedStringArray[/code], and [code]target_folder: String[/code]).
+ </description>
+ </method>
+ <method name="ssh_push_to_remote" qualifiers="const">
+ <return type="int" enum="Error" />
+ <param index="0" name="host" type="String" />
+ <param index="1" name="port" type="String" />
+ <param index="2" name="scp_args" type="PackedStringArray" />
+ <param index="3" name="src_file" type="String" />
+ <param index="4" name="dst_file" type="String" />
+ <description>
+ Uploads specified file over SCP protocol to the remote host.
+ </description>
+ </method>
+ <method name="ssh_run_on_remote" qualifiers="const">
+ <return type="int" enum="Error" />
+ <param index="0" name="host" type="String" />
+ <param index="1" name="port" type="String" />
+ <param index="2" name="ssh_arg" type="PackedStringArray" />
+ <param index="3" name="cmd_args" type="String" />
+ <param index="4" name="output" type="Array" default="[]" />
+ <param index="5" name="port_fwd" type="int" default="-1" />
+ <description>
+ Executes specified command on the remote host via SSH protocol and returns command output in the [param output].
+ </description>
+ </method>
+ <method name="ssh_run_on_remote_no_wait" qualifiers="const">
+ <return type="int" />
+ <param index="0" name="host" type="String" />
+ <param index="1" name="port" type="String" />
+ <param index="2" name="ssh_args" type="PackedStringArray" />
+ <param index="3" name="cmd_args" type="String" />
+ <param index="4" name="port_fwd" type="int" default="-1" />
+ <description>
+ Executes specified command on the remote host via SSH protocol and returns process ID (on the remote host) without waiting for command to finish.
+ </description>
+ </method>
</methods>
+ <constants>
+ <constant name="EXPORT_MESSAGE_NONE" value="0" enum="ExportMessageType">
+ Invalid message type used as the default value when no type is specified.
+ </constant>
+ <constant name="EXPORT_MESSAGE_INFO" value="1" enum="ExportMessageType">
+ Message type for informational messages that have no effect on the export.
+ </constant>
+ <constant name="EXPORT_MESSAGE_WARNING" value="2" enum="ExportMessageType">
+ Message type for warning messages that should be addressed but still allow to complete the export.
+ </constant>
+ <constant name="EXPORT_MESSAGE_ERROR" value="3" enum="ExportMessageType">
+ Message type for error messages that must be addressed and fail the export.
+ </constant>
+ <constant name="DEBUG_FLAG_DUMB_CLIENT" value="1" enum="DebugFlags" is_bitfield="true">
+ Flag is set if remotely debugged project is expected to use remote file system. If set, [method gen_export_flags] will add [code]--remove-fs[/code] and [code]--remote-fs-password[/code] (if password is set in the editor settings) command line arguments to the list.
+ </constant>
+ <constant name="DEBUG_FLAG_REMOTE_DEBUG" value="2" enum="DebugFlags" is_bitfield="true">
+ Flag is set if remote debug is enabled. If set, [method gen_export_flags] will add [code]--remote-debug[/code] and [code]--breakpoints[/code] (if breakpoints are selected in the script editor or added by the plugin) command line arguments to the list.
+ </constant>
+ <constant name="DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST" value="4" enum="DebugFlags" is_bitfield="true">
+ Flag is set if remotely debugged project is running on the localhost. If set, [method gen_export_flags] will use [code]localhost[/code] instead of [member EditorSettings.network/debug/remote_host] as remote debugger host.
+ </constant>
+ <constant name="DEBUG_FLAG_VIEW_COLLISIONS" value="8" enum="DebugFlags" is_bitfield="true">
+ Flag is set if "Visible Collision Shapes" remote debug option is enabled. If set, [method gen_export_flags] will add [code]--debug-collisions[/code] command line arguments to the list.
+ </constant>
+ <constant name="DEBUG_FLAG_VIEW_NAVIGATION" value="16" enum="DebugFlags" is_bitfield="true">
+ Flag is set if Visible Navigation" remote debug option is enabled. If set, [method gen_export_flags] will add [code]--debug-navigation[/code] command line arguments to the list.
+ </constant>
+ </constants>
</class>
diff --git a/doc/classes/EditorExportPlatformExtension.xml b/doc/classes/EditorExportPlatformExtension.xml
new file mode 100644
index 0000000000..ef589e2f58
--- /dev/null
+++ b/doc/classes/EditorExportPlatformExtension.xml
@@ -0,0 +1,282 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EditorExportPlatformExtension" inherits="EditorExportPlatform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ Base class for custom [EditorExportPlatform] implementations (plugins).
+ </brief_description>
+ <description>
+ External [EditorExportPlatform] implementations should inherit from this class.
+ To use [EditorExportPlatform], register it using the [method EditorPlugin.add_export_platform] method first.
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="_can_export" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <description>
+ [b]Optional.[/b]
+ Returns [code]true[/code], if specified [param preset] is valid and can be exported. Use [method set_config_error] and [method set_config_missing_templates] to set error details.
+ Usual implementation can call [method _has_valid_export_configuration] and [method _has_valid_project_configuration] to determine if export is possible.
+ </description>
+ </method>
+ <method name="_cleanup" qualifiers="virtual">
+ <return type="void" />
+ <description>
+ [b]Optional.[/b]
+ Called by the editor before platform is unregistered.
+ </description>
+ </method>
+ <method name="_export_pack" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ [b]Optional.[/b]
+ Creates a PCK archive at [param path] for the specified [param preset].
+ This method is called when "Export PCK/ZIP" button is pressed in the export dialog, and PCK is selected as a file type.
+ </description>
+ </method>
+ <method name="_export_project" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ [b]Required.[/b]
+ Creates a full project at [param path] for the specified [param preset].
+ This method is called when "Export" button is pressed in the export dialog.
+ This method implementation can call [method EditorExportPlatform.save_pack] or [method EditorExportPlatform.save_zip] to use default PCK/ZIP export process, or calls [method EditorExportPlatform.export_project_files] and implement custom callback for processing each exported file.
+ </description>
+ </method>
+ <method name="_export_zip" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <param index="2" name="path" type="String" />
+ <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ [b]Optional.[/b]
+ Create a ZIP archive at [param path] for the specified [param preset].
+ This method is called when "Export PCK/ZIP" button is pressed in the export dialog, and ZIP is selected as a file type.
+ </description>
+ </method>
+ <method name="_get_binary_extensions" qualifiers="virtual const">
+ <return type="PackedStringArray" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <description>
+ [b]Required.[/b]
+ Returns array of supported binary extensions for the full project export.
+ </description>
+ </method>
+ <method name="_get_debug_protocol" qualifiers="virtual const">
+ <return type="String" />
+ <description>
+ [b]Optional.[/b]
+ Returns protocol used for remote debugging. Default implementation return [code]tcp://[/code].
+ </description>
+ </method>
+ <method name="_get_device_architecture" qualifiers="virtual const">
+ <return type="String" />
+ <param index="0" name="device" type="int" />
+ <description>
+ [b]Optional.[/b]
+ Returns device architecture for one-click deploy.
+ </description>
+ </method>
+ <method name="_get_export_option_visibility" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="option" type="String" />
+ <description>
+ [b]Optional.[/b]
+ Validates [param option] and returns visibility for the specified [param preset]. Default implementation return [code]true[/code] for all options.
+ </description>
+ </method>
+ <method name="_get_export_option_warning" qualifiers="virtual const">
+ <return type="String" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="option" type="StringName" />
+ <description>
+ [b]Optional.[/b]
+ Validates [param option] and returns warning message for the specified [param preset]. Default implementation return empty string for all options.
+ </description>
+ </method>
+ <method name="_get_export_options" qualifiers="virtual const">
+ <return type="Dictionary[]" />
+ <description>
+ [b]Optional.[/b]
+ Returns a property list, as an [Array] of dictionaries. Each [Dictionary] must at least contain the [code]name: StringName[/code] and [code]type: Variant.Type[/code] entries.
+ Additionally, the following keys are supported:
+ - [code]hint: PropertyHint[/code]
+ - [code]hint_string: String[/code]
+ - [code]usage: PropertyUsageFlags[/code]
+ - [code]class_name: StringName[/code]
+ - [code]default_value: Variant[/code], default value of the property.
+ - [code]update_visibility: bool[/code], if set to [code]true[/code], [method _get_export_option_visibility] is called for each property when this property is changed.
+ - [code]required: bool[/code], if set to [code]true[/code], this property warnings are critical, and should be resolved to make export possible. This value is a hint for the [method _has_valid_export_configuration] implementation, and not used by the engine directly.
+ See also [method Object._get_property_list].
+ </description>
+ </method>
+ <method name="_get_logo" qualifiers="virtual const">
+ <return type="Texture2D" />
+ <description>
+ [b]Required.[/b]
+ Returns platform logo displayed in the export dialog, logo should be 32x32 adjusted to the current editor scale, see [method EditorInterface.get_editor_scale].
+ </description>
+ </method>
+ <method name="_get_name" qualifiers="virtual const">
+ <return type="String" />
+ <description>
+ [b]Required.[/b]
+ Returns export platform name.
+ </description>
+ </method>
+ <method name="_get_option_icon" qualifiers="virtual const">
+ <return type="ImageTexture" />
+ <param index="0" name="device" type="int" />
+ <description>
+ [b]Optional.[/b]
+ Returns one-click deploy menu item icon for the specified [param device], icon should be 16x16 adjusted to the current editor scale, see [method EditorInterface.get_editor_scale].
+ </description>
+ </method>
+ <method name="_get_option_label" qualifiers="virtual const">
+ <return type="String" />
+ <param index="0" name="device" type="int" />
+ <description>
+ [b]Optional.[/b]
+ Returns one-click deploy menu item label for the specified [param device].
+ </description>
+ </method>
+ <method name="_get_option_tooltip" qualifiers="virtual const">
+ <return type="String" />
+ <param index="0" name="device" type="int" />
+ <description>
+ [b]Optional.[/b]
+ Returns one-click deploy menu item tooltip for the specified [param device].
+ </description>
+ </method>
+ <method name="_get_options_count" qualifiers="virtual const">
+ <return type="int" />
+ <description>
+ [b]Optional.[/b]
+ Returns number one-click deploy devices (or other one-click option displayed in the menu).
+ </description>
+ </method>
+ <method name="_get_options_tooltip" qualifiers="virtual const">
+ <return type="String" />
+ <description>
+ [b]Optional.[/b]
+ Returns tooltip of the one-click deploy menu button.
+ </description>
+ </method>
+ <method name="_get_os_name" qualifiers="virtual const">
+ <return type="String" />
+ <description>
+ [b]Required.[/b]
+ Returns target OS name.
+ </description>
+ </method>
+ <method name="_get_platform_features" qualifiers="virtual const">
+ <return type="PackedStringArray" />
+ <description>
+ [b]Required.[/b]
+ Returns array of platform specific features.
+ </description>
+ </method>
+ <method name="_get_preset_features" qualifiers="virtual const">
+ <return type="PackedStringArray" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <description>
+ [b]Required.[/b]
+ Returns array of platform specific features for the specified [param preset].
+ </description>
+ </method>
+ <method name="_get_run_icon" qualifiers="virtual const">
+ <return type="Texture2D" />
+ <description>
+ [b]Optional.[/b]
+ Returns icon of the one-click deploy menu button, icon should be 16x16 adjusted to the current editor scale, see [method EditorInterface.get_editor_scale].
+ </description>
+ </method>
+ <method name="_has_valid_export_configuration" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="debug" type="bool" />
+ <description>
+ [b]Required.[/b]
+ Returns [code]true[/code] if export configuration is valid.
+ </description>
+ </method>
+ <method name="_has_valid_project_configuration" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <description>
+ [b]Required.[/b]
+ Returns [code]true[/code] if project configuration is valid.
+ </description>
+ </method>
+ <method name="_is_executable" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="path" type="String" />
+ <description>
+ [b]Optional.[/b]
+ Returns [code]true[/code] if specified file is a valid executable (native executable or script) for the target platform.
+ </description>
+ </method>
+ <method name="_poll_export" qualifiers="virtual">
+ <return type="bool" />
+ <description>
+ [b]Optional.[/b]
+ Returns [code]true[/code] if one-click deploy options are changed and editor interface should be updated.
+ </description>
+ </method>
+ <method name="_run" qualifiers="virtual">
+ <return type="int" enum="Error" />
+ <param index="0" name="preset" type="EditorExportPreset" />
+ <param index="1" name="device" type="int" />
+ <param index="2" name="debug_flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" />
+ <description>
+ [b]Optional.[/b]
+ This method is called when [param device] one-click deploy menu option is selected.
+ Implementation should export project to a temporary location, upload and run it on the specific [param device], or perform another action associated with the menu item.
+ </description>
+ </method>
+ <method name="_should_update_export_options" qualifiers="virtual">
+ <return type="bool" />
+ <description>
+ [b]Optional.[/b]
+ Returns [code]true[/code] if export options list is changed and presets should be updated.
+ </description>
+ </method>
+ <method name="get_config_error" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns current configuration error message text. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations.
+ </description>
+ </method>
+ <method name="get_config_missing_templates" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] is export templates are missing from the current configuration. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations.
+ </description>
+ </method>
+ <method name="set_config_error" qualifiers="const">
+ <return type="void" />
+ <param index="0" name="error_text" type="String" />
+ <description>
+ Sets current configuration error message text. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations.
+ </description>
+ </method>
+ <method name="set_config_missing_templates" qualifiers="const">
+ <return type="void" />
+ <param index="0" name="missing_templates" type="bool" />
+ <description>
+ Set to [code]true[/code] is export templates are missing from the current configuration. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations.
+ </description>
+ </method>
+ </methods>
+</class>
diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml
index 9ef911a68d..42e1968eb0 100644
--- a/doc/classes/EditorExportPlugin.xml
+++ b/doc/classes/EditorExportPlugin.xml
@@ -305,6 +305,18 @@
In case of a directory code-sign will error if you place non code object in directory.
</description>
</method>
+ <method name="get_export_platform" qualifiers="const">
+ <return type="EditorExportPlatform" />
+ <description>
+ Returns currently used export platform.
+ </description>
+ </method>
+ <method name="get_export_preset" qualifiers="const">
+ <return type="EditorExportPreset" />
+ <description>
+ Returns currently used export preset.
+ </description>
+ </method>
<method name="get_option" qualifiers="const">
<return type="Variant" />
<param index="0" name="name" type="StringName" />
diff --git a/doc/classes/EditorExportPreset.xml b/doc/classes/EditorExportPreset.xml
new file mode 100644
index 0000000000..bba79364e4
--- /dev/null
+++ b/doc/classes/EditorExportPreset.xml
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EditorExportPreset" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ Export preset configuration.
+ </brief_description>
+ <description>
+ Export preset configuration. Instances of [EditorExportPreset] by editor UI and intended to be used a read-only configuration passed to the [EditorExportPlatform] methods when exporting the project.
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="are_advanced_options_enabled" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code], is "Advanced" toggle is enabled in the export dialog.
+ </description>
+ </method>
+ <method name="get_custom_features" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns string with a comma separated list of custom features.
+ </description>
+ </method>
+ <method name="get_customized_files" qualifiers="const">
+ <return type="Dictionary" />
+ <description>
+ Returns [Dictionary] of files selected in the "Resources" tab of the export dialog. Dictionary keys are file names and values are export mode - [code]"strip[/code], [code]"keep"[/code], or [code]"remove"[/code]. See also [method get_file_export_mode].
+ </description>
+ </method>
+ <method name="get_customized_files_count" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns number of files selected in the "Resources" tab of the export dialog.
+ </description>
+ </method>
+ <method name="get_encrypt_directory" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code], PCK directory encryption is enabled in the export dialog.
+ </description>
+ </method>
+ <method name="get_encrypt_pck" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code], PCK encryption is enabled in the export dialog.
+ </description>
+ </method>
+ <method name="get_encryption_ex_filter" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns file filters to exclude during PCK encryption.
+ </description>
+ </method>
+ <method name="get_encryption_in_filter" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns file filters to include during PCK encryption.
+ </description>
+ </method>
+ <method name="get_encryption_key" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns PCK encryption key.
+ </description>
+ </method>
+ <method name="get_exclude_filter" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns file filters to exclude during export.
+ </description>
+ </method>
+ <method name="get_export_filter" qualifiers="const">
+ <return type="int" enum="EditorExportPreset.ExportFilter" />
+ <description>
+ Returns export file filter mode selected in the "Resources" tab of the export dialog.
+ </description>
+ </method>
+ <method name="get_export_path" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns export target path.
+ </description>
+ </method>
+ <method name="get_file_export_mode" qualifiers="const">
+ <return type="int" enum="EditorExportPreset.FileExportMode" />
+ <param index="0" name="path" type="String" />
+ <param index="1" name="default" type="int" enum="EditorExportPreset.FileExportMode" default="0" />
+ <description>
+ Returns file export mode for the specified file.
+ </description>
+ </method>
+ <method name="get_files_to_export" qualifiers="const">
+ <return type="PackedStringArray" />
+ <description>
+ Returns array of files to export.
+ </description>
+ </method>
+ <method name="get_include_filter" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns file filters to include during export.
+ </description>
+ </method>
+ <method name="get_or_env" qualifiers="const">
+ <return type="Variant" />
+ <param index="0" name="name" type="StringName" />
+ <param index="1" name="env_var" type="String" />
+ <description>
+ Returns export option value or value of environment variable if it is set.
+ </description>
+ </method>
+ <method name="get_preset_name" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns export preset name.
+ </description>
+ </method>
+ <method name="get_script_export_mode" qualifiers="const">
+ <return type="int" />
+ <description>
+ Returns script export mode.
+ </description>
+ </method>
+ <method name="get_version" qualifiers="const">
+ <return type="String" />
+ <param index="0" name="name" type="StringName" />
+ <param index="1" name="windows_version" type="bool" />
+ <description>
+ Returns the preset's version number, or fall back to the [member ProjectSettings.application/config/version] project setting if set to an empty string.
+ If [param windows_version] is [code]true[/code], formats the returned version number to be compatible with Windows executable metadata.
+ </description>
+ </method>
+ <method name="has" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="property" type="StringName" />
+ <description>
+ Returns [code]true[/code] if preset has specified property.
+ </description>
+ </method>
+ <method name="has_export_file">
+ <return type="bool" />
+ <param index="0" name="path" type="String" />
+ <description>
+ Returns [code]true[/code] if specified file is exported.
+ </description>
+ </method>
+ <method name="is_dedicated_server" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if dedicated server export mode is selected in the export dialog.
+ </description>
+ </method>
+ <method name="is_runnable" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if "Runnable" toggle is enabled in the export dialog.
+ </description>
+ </method>
+ </methods>
+ <constants>
+ <constant name="EXPORT_ALL_RESOURCES" value="0" enum="ExportFilter">
+ </constant>
+ <constant name="EXPORT_SELECTED_SCENES" value="1" enum="ExportFilter">
+ </constant>
+ <constant name="EXPORT_SELECTED_RESOURCES" value="2" enum="ExportFilter">
+ </constant>
+ <constant name="EXCLUDE_SELECTED_RESOURCES" value="3" enum="ExportFilter">
+ </constant>
+ <constant name="EXPORT_CUSTOMIZED" value="4" enum="ExportFilter">
+ </constant>
+ <constant name="MODE_FILE_NOT_CUSTOMIZED" value="0" enum="FileExportMode">
+ </constant>
+ <constant name="MODE_FILE_STRIP" value="1" enum="FileExportMode">
+ </constant>
+ <constant name="MODE_FILE_KEEP" value="2" enum="FileExportMode">
+ </constant>
+ <constant name="MODE_FILE_REMOVE" value="3" enum="FileExportMode">
+ </constant>
+ <constant name="MODE_SCRIPT_TEXT" value="0" enum="ScriptExportMode">
+ </constant>
+ <constant name="MODE_SCRIPT_BINARY_TOKENS" value="1" enum="ScriptExportMode">
+ </constant>
+ <constant name="MODE_SCRIPT_BINARY_TOKENS_COMPRESSED" value="2" enum="ScriptExportMode">
+ </constant>
+ </constants>
+</class>
diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml
index 178be439c3..6809d4ac93 100644
--- a/doc/classes/EditorInterface.xml
+++ b/doc/classes/EditorInterface.xml
@@ -117,6 +117,12 @@
[b]Note:[/b] When creating custom editor UI, prefer accessing theme items directly from your GUI nodes using the [code]get_theme_*[/code] methods.
</description>
</method>
+ <method name="get_editor_undo_redo" qualifiers="const">
+ <return type="EditorUndoRedoManager" />
+ <description>
+ Returns the editor's [EditorUndoRedoManager].
+ </description>
+ </method>
<method name="get_editor_viewport_2d" qualifiers="const">
<return type="SubViewport" />
<description>
diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml
index ed3233b1ae..37f8b2213b 100644
--- a/doc/classes/EditorPlugin.xml
+++ b/doc/classes/EditorPlugin.xml
@@ -455,6 +455,13 @@
Adds a [Script] as debugger plugin to the Debugger. The script must extend [EditorDebuggerPlugin].
</description>
</method>
+ <method name="add_export_platform">
+ <return type="void" />
+ <param index="0" name="platform" type="EditorExportPlatform" />
+ <description>
+ Registers a new [EditorExportPlatform]. Export platforms provides functionality of exporting to the specific platform.
+ </description>
+ </method>
<method name="add_export_plugin">
<return type="void" />
<param index="0" name="plugin" type="EditorExportPlugin" />
@@ -653,6 +660,13 @@
Removes the debugger plugin with given script from the Debugger.
</description>
</method>
+ <method name="remove_export_platform">
+ <return type="void" />
+ <param index="0" name="platform" type="EditorExportPlatform" />
+ <description>
+ Removes an export platform registered by [method add_export_platform].
+ </description>
+ </method>
<method name="remove_export_plugin">
<return type="void" />
<param index="0" name="plugin" type="EditorExportPlugin" />
diff --git a/doc/classes/EditorResourcePreviewGenerator.xml b/doc/classes/EditorResourcePreviewGenerator.xml
index fcfdbb5c44..9c9b6d11b2 100644
--- a/doc/classes/EditorResourcePreviewGenerator.xml
+++ b/doc/classes/EditorResourcePreviewGenerator.xml
@@ -23,7 +23,7 @@
<param index="2" name="metadata" type="Dictionary" />
<description>
Generate a preview from a given resource with the specified size. This must always be implemented.
- Returning an empty texture is an OK way to fail and let another generator take care.
+ Returning [code]null[/code] is an OK way to fail and let another generator take care.
Care must be taken because this function is always called from a thread (not the main thread).
[param metadata] dictionary can be modified to store file-specific metadata that can be used in [method EditorResourceTooltipPlugin._make_tooltip_for_path] (like image size, sample length etc.).
</description>
@@ -35,7 +35,7 @@
<param index="2" name="metadata" type="Dictionary" />
<description>
Generate a preview directly from a path with the specified size. Implementing this is optional, as default code will load and call [method _generate].
- Returning an empty texture is an OK way to fail and let another generator take care.
+ Returning [code]null[/code] is an OK way to fail and let another generator take care.
Care must be taken because this function is always called from a thread (not the main thread).
[param metadata] dictionary can be modified to store file-specific metadata that can be used in [method EditorResourceTooltipPlugin._make_tooltip_for_path] (like image size, sample length etc.).
</description>
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index 4bb7149f2f..b6565b81f2 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -258,11 +258,14 @@
The color to use when drawing smart snapping lines in the 2D editor. The smart snapping lines will automatically display when moving 2D nodes if smart snapping is enabled in the Snapping Options menu at the top of the 2D editor viewport.
</member>
<member name="editors/2d/use_integer_zoom_by_default" type="bool" setter="" getter="">
- If [code]true[/code], the 2D editor will snap to integer zoom values while not holding the [kbd]Alt[/kbd] key and powers of two while holding it. If [code]false[/code], this behavior is swapped.
+ If [code]true[/code], the 2D editor will snap to integer zoom values when not holding the [kbd]Alt[/kbd] key. If [code]false[/code], this behavior is swapped.
</member>
<member name="editors/2d/viewport_border_color" type="Color" setter="" getter="">
The color of the viewport border in the 2D editor. This border represents the viewport's size at the base resolution defined in the Project Settings. Objects placed outside this border will not be visible unless a [Camera2D] node is used, or unless the window is resized and the stretch mode is set to [code]disabled[/code].
</member>
+ <member name="editors/2d/zoom_speed_factor" type="float" setter="" getter="">
+ The factor to use when zooming in or out in the 2D editor. For example, [code]1.1[/code] will zoom in by 10% with every step. If set to [code]2.0[/code], zooming will only cycle through powers of two.
+ </member>
<member name="editors/3d/default_fov" type="float" setter="" getter="">
The default camera vertical field of view to use in the 3D editor (in degrees). The camera field of view can be adjusted on a per-scene basis using the [b]View[/b] menu at the top of the 3D editor. If a scene had its camera field of view adjusted using the [b]View[/b] menu, this setting is ignored in the scene in question. This setting is also ignored while a [Camera3D] node is being previewed in the editor.
[b]Note:[/b] The editor camera always uses the [b]Keep Height[/b] aspect mode.
@@ -321,7 +324,6 @@
<member name="editors/3d/navigation/emulate_3_button_mouse" type="bool" setter="" getter="">
If [code]true[/code], enables 3-button mouse emulation mode. This is useful on laptops when using a trackpad.
When 3-button mouse emulation mode is enabled, the pan, zoom and orbit modifiers can always be used in the 3D editor viewport, even when not holding down any mouse button.
- [b]Note:[/b] No matter the orbit modifier configured in [member editors/3d/navigation/orbit_modifier], [kbd]Alt[/kbd] will always remain usable for orbiting in this mode to improve usability with graphics tablets.
</member>
<member name="editors/3d/navigation/emulate_numpad" type="bool" setter="" getter="">
If [code]true[/code], allows using the top row [kbd]0[/kbd]-[kbd]9[/kbd] keys to function as their equivalent numpad keys for 3D editor navigation. This should be enabled on keyboards that have no numeric keypad available.
@@ -333,28 +335,25 @@
If [code]true[/code], invert the vertical mouse axis when panning, orbiting, or using freelook mode in the 3D editor.
</member>
<member name="editors/3d/navigation/navigation_scheme" type="int" setter="" getter="">
- The navigation scheme to use in the 3D editor. Changing this setting will affect the mouse buttons that must be held down to perform certain operations in the 3D editor viewport.
- - [b]Godot[/b] Middle mouse button to orbit, [kbd]Shift + Middle mouse button[/kbd] to pan. [kbd]Mouse wheel[/kbd] to zoom.
- - [b]Maya:[/b] [kbd]Alt + Left mouse button[/kbd] to orbit. [kbd]Middle mouse button[/kbd] to pan, [kbd]Shift + Middle mouse button[/kbd] to pan 10 times faster. [kbd]Mouse wheel[/kbd] to zoom.
+ The navigation scheme preset to use in the 3D editor. Changing this setting will affect the mouse button and modifier controls used to navigate the 3D editor viewport.
+ All schemes can use [kbd]Mouse wheel[/kbd] to zoom.
+ - [b]Godot:[/b] [kbd]Middle mouse button[/kbd] to orbit. [kbd]Shift + Middle mouse button[/kbd] to pan. [kbd]Ctrl + Shift + Middle mouse button[/kbd] to zoom.
+ - [b]Maya:[/b] [kbd]Alt + Left mouse button[/kbd] to orbit. [kbd]Middle mouse button[/kbd] to pan, [kbd]Shift + Middle mouse button[/kbd] to pan 10 times faster. [kbd]Alt + Right mouse button[/kbd] to zoom.
- [b]Modo:[/b] [kbd]Alt + Left mouse button[/kbd] to orbit. [kbd]Alt + Shift + Left mouse button[/kbd] to pan. [kbd]Ctrl + Alt + Left mouse button[/kbd] to zoom.
- See also [member editors/3d/freelook/freelook_navigation_scheme].
+ See also [member editors/3d/navigation/orbit_mouse_button], [member editors/3d/navigation/pan_mouse_button], [member editors/3d/navigation/zoom_mouse_button], and [member editors/3d/freelook/freelook_navigation_scheme].
[b]Note:[/b] On certain window managers on Linux, the [kbd]Alt[/kbd] key will be intercepted by the window manager when clicking a mouse button at the same time. This means Godot will not see the modifier key as being pressed.
</member>
- <member name="editors/3d/navigation/orbit_modifier" type="int" setter="" getter="">
- The modifier key that must be held to orbit in the 3D editor.
- [b]Note:[/b] If [member editors/3d/navigation/emulate_3_button_mouse] is [code]true[/code], [kbd]Alt[/kbd] will always remain usable for orbiting to improve usability with graphics tablets.
- [b]Note:[/b] On certain window managers on Linux, the [kbd]Alt[/kbd] key will be intercepted by the window manager when clicking a mouse button at the same time. This means Godot will not see the modifier key as being pressed.
+ <member name="editors/3d/navigation/orbit_mouse_button" type="int" setter="" getter="">
+ The mouse button that needs to be held down to orbit in the 3D editor viewport.
</member>
- <member name="editors/3d/navigation/pan_modifier" type="int" setter="" getter="">
- The modifier key that must be held to pan in the 3D editor.
- [b]Note:[/b] On certain window managers on Linux, the [kbd]Alt[/kbd] key will be intercepted by the window manager when clicking a mouse button at the same time. This means Godot will not see the modifier key as being pressed.
+ <member name="editors/3d/navigation/pan_mouse_button" type="int" setter="" getter="">
+ The mouse button that needs to be held down to pan in the 3D editor viewport.
</member>
<member name="editors/3d/navigation/warped_mouse_panning" type="bool" setter="" getter="">
If [code]true[/code], warps the mouse around the 3D viewport while panning in the 3D editor. This makes it possible to pan over a large area without having to exit panning and adjust the mouse cursor.
</member>
- <member name="editors/3d/navigation/zoom_modifier" type="int" setter="" getter="">
- The modifier key that must be held to zoom in the 3D editor.
- [b]Note:[/b] On certain window managers on Linux, the [kbd]Alt[/kbd] key will be intercepted by the window manager when clicking a mouse button at the same time. This means Godot will not see the modifier key as being pressed.
+ <member name="editors/3d/navigation/zoom_mouse_button" type="int" setter="" getter="">
+ The mouse button that needs to be held down to zoom in the 3D editor viewport.
</member>
<member name="editors/3d/navigation/zoom_style" type="int" setter="" getter="">
The mouse cursor movement direction to use when zooming by moving the mouse. This does not affect zooming with the mouse wheel.
@@ -677,6 +676,9 @@
<member name="interface/editor/import_resources_when_unfocused" type="bool" setter="" getter="">
If [code]true[/code], (re)imports resources even if the editor window is unfocused or minimized. If [code]false[/code], resources are only (re)imported when the editor window is focused. This can be set to [code]true[/code] to speed up iteration by starting the import process earlier when saving files in the project folder. This also allows getting visual feedback on changes without having to click the editor window, which is useful with multi-monitor setups. The downside of setting this to [code]true[/code] is that it increases idle CPU usage and may steal CPU time from other applications when importing resources.
</member>
+ <member name="interface/editor/keep_screen_on" type="bool" setter="" getter="">
+ If [code]true[/code], keeps the screen on (even in case of inactivity), so the screensaver does not take over. Works on desktop and mobile platforms.
+ </member>
<member name="interface/editor/localize_settings" type="bool" setter="" getter="">
If [code]true[/code], setting names in the editor are localized when possible.
[b]Note:[/b] This setting affects most [EditorInspector]s in the editor UI, primarily Project Settings and Editor Settings. To control names displayed in the Inspector dock, use [member interface/inspector/default_property_name_style] instead.
@@ -961,7 +963,17 @@
If [code]true[/code], on Linux/BSD, the editor will check for Wayland first instead of X11 (if available).
</member>
<member name="run/window_placement/android_window" type="int" setter="" getter="">
- The Android window to display the project on when starting the project from the editor.
+ Specifies how the Play window is launched relative to the Android editor.
+ - [b]Auto (based on screen size)[/b] (default) will automatically choose how to launch the Play window based on the device and screen metrics. Defaults to [b]Same as Editor[/b] on phones and [b]Side-by-side with Editor[/b] on tablets.
+ - [b]Same as Editor[/b] will launch the Play window in the same window as the Editor.
+ - [b]Side-by-side with Editor[/b] will launch the Play window side-by-side with the Editor window.
+ [b]Note:[/b] Only available in the Android editor.
+ </member>
+ <member name="run/window_placement/play_window_pip_mode" type="int" setter="" getter="">
+ Specifies the picture-in-picture (PiP) mode for the Play window.
+ - [b]Disabled:[/b] PiP is disabled for the Play window.
+ - [b]Enabled:[/b] If the device supports it, PiP is always enabled for the Play window. The Play window will contain a button to enter PiP mode.
+ - [b]Enabled when Play window is same as Editor[/b] (default for Android editor): If the device supports it, PiP is enabled when the Play window is the same as the Editor. The Play window will contain a button to enter PiP mode.
[b]Note:[/b] Only available in the Android editor.
</member>
<member name="run/window_placement/rect" type="int" setter="" getter="">
diff --git a/doc/classes/EditorSpinSlider.xml b/doc/classes/EditorSpinSlider.xml
index 783f1243e2..83c65b736e 100644
--- a/doc/classes/EditorSpinSlider.xml
+++ b/doc/classes/EditorSpinSlider.xml
@@ -51,4 +51,12 @@
</description>
</signal>
</signals>
+ <theme_items>
+ <theme_item name="updown" data_type="icon" type="Texture2D">
+ Single texture representing both the up and down buttons.
+ </theme_item>
+ <theme_item name="updown_disabled" data_type="icon" type="Texture2D">
+ Single texture representing both the up and down buttons, when the control is readonly or disabled.
+ </theme_item>
+ </theme_items>
</class>
diff --git a/doc/classes/EditorUndoRedoManager.xml b/doc/classes/EditorUndoRedoManager.xml
index 5ac0d790a2..0f8c69a1bb 100644
--- a/doc/classes/EditorUndoRedoManager.xml
+++ b/doc/classes/EditorUndoRedoManager.xml
@@ -68,6 +68,21 @@
Register a reference for "undo" that will be erased if the "undo" history is lost. This is useful mostly for nodes removed with the "do" call (not the "undo" call!).
</description>
</method>
+ <method name="clear_history">
+ <return type="void" />
+ <param index="0" name="id" type="int" default="-99" />
+ <param index="1" name="increase_version" type="bool" default="true" />
+ <description>
+ Clears the given undo history. You can clear history for a specific scene, global history, or for all scenes at once if [param id] is [constant INVALID_HISTORY].
+ If [param increase_version] is [code]true[/code], the undo history version will be increased, marking it as unsaved. Useful for operations that modify the scene, but don't support undo.
+ [codeblock]
+ var scene_root = EditorInterface.get_edited_scene_root()
+ var undo_redo = EditorInterface.get_editor_undo_redo()
+ undo_redo.clear_history(undo_redo.get_object_history_id(scene_root))
+ [/codeblock]
+ [b]Note:[/b] If you want to mark an edited scene as unsaved without clearing its history, use [method EditorInterface.mark_scene_as_unsaved] instead.
+ </description>
+ </method>
<method name="commit_action">
<return type="void" />
<param index="0" name="execute" type="bool" default="true" />
diff --git a/doc/classes/GDExtensionManager.xml b/doc/classes/GDExtensionManager.xml
index 211bc023c0..97d2d08752 100644
--- a/doc/classes/GDExtensionManager.xml
+++ b/doc/classes/GDExtensionManager.xml
@@ -56,6 +56,20 @@
</method>
</methods>
<signals>
+ <signal name="extension_loaded">
+ <param index="0" name="extension" type="GDExtension" />
+ <description>
+ Emitted after the editor has finished loading a new extension.
+ [b]Note:[/b] This signal is only emitted in editor builds.
+ </description>
+ </signal>
+ <signal name="extension_unloading">
+ <param index="0" name="extension" type="GDExtension" />
+ <description>
+ Emitted before the editor starts unloading an extension.
+ [b]Note:[/b] This signal is only emitted in editor builds.
+ </description>
+ </signal>
<signal name="extensions_reloaded">
<description>
Emitted after the editor has finished reloading one or more extensions.
diff --git a/doc/classes/ImporterMesh.xml b/doc/classes/ImporterMesh.xml
index eeabcd6e12..28ee5710d9 100644
--- a/doc/classes/ImporterMesh.xml
+++ b/doc/classes/ImporterMesh.xml
@@ -191,8 +191,4 @@
</description>
</method>
</methods>
- <members>
- <member name="_data" type="Dictionary" setter="_set_data" getter="_get_data" default="{ &quot;surfaces&quot;: [] }">
- </member>
- </members>
</class>
diff --git a/doc/classes/JSON.xml b/doc/classes/JSON.xml
index 8a19aa39bf..fe5fdfa89a 100644
--- a/doc/classes/JSON.xml
+++ b/doc/classes/JSON.xml
@@ -37,6 +37,16 @@
<tutorials>
</tutorials>
<methods>
+ <method name="from_native" qualifiers="static">
+ <return type="Variant" />
+ <param index="0" name="variant" type="Variant" />
+ <param index="1" name="allow_classes" type="bool" default="false" />
+ <param index="2" name="allow_scripts" type="bool" default="false" />
+ <description>
+ Converts a native engine type to a JSON-compliant dictionary.
+ By default, classes and scripts are ignored for security reasons, unless [param allow_classes] or [param allow_scripts] are specified.
+ </description>
+ </method>
<method name="get_error_line" qualifiers="const">
<return type="int" />
<description>
@@ -123,6 +133,16 @@
[/codeblock]
</description>
</method>
+ <method name="to_native" qualifiers="static">
+ <return type="Variant" />
+ <param index="0" name="json" type="Variant" />
+ <param index="1" name="allow_classes" type="bool" default="false" />
+ <param index="2" name="allow_scripts" type="bool" default="false" />
+ <description>
+ Converts a JSON-compliant dictionary that was created with [method from_native] back to native engine types.
+ By default, classes and scripts are ignored for security reasons, unless [param allow_classes] or [param allow_scripts] are specified.
+ </description>
+ </method>
</methods>
<members>
<member name="data" type="Variant" setter="set_data" getter="get_data" default="null">
diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml
index 8acd05cbd1..e6eba30ab7 100644
--- a/doc/classes/Label.xml
+++ b/doc/classes/Label.xml
@@ -59,7 +59,7 @@
Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants.
</member>
<member name="justification_flags" type="int" setter="set_justification_flags" getter="get_justification_flags" enum="TextServer.JustificationFlag" is_bitfield="true" default="163">
- Line fill alignment rules. For more info see [enum TextServer.JustificationFlag].
+ Line fill alignment rules. See [enum TextServer.JustificationFlag] for more information.
</member>
<member name="label_settings" type="LabelSettings" setter="set_label_settings" getter="get_label_settings">
A [LabelSettings] resource that can be shared between multiple [Label] nodes. Takes priority over theme properties.
diff --git a/doc/classes/Label3D.xml b/doc/classes/Label3D.xml
index 4c70897452..ff26c5490d 100644
--- a/doc/classes/Label3D.xml
+++ b/doc/classes/Label3D.xml
@@ -73,7 +73,7 @@
Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants.
</member>
<member name="justification_flags" type="int" setter="set_justification_flags" getter="get_justification_flags" enum="TextServer.JustificationFlag" is_bitfield="true" default="163">
- Line fill alignment rules. For more info see [enum TextServer.JustificationFlag].
+ Line fill alignment rules. See [enum TextServer.JustificationFlag] for more information.
</member>
<member name="language" type="String" setter="set_language" getter="get_language" default="&quot;&quot;">
Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead.
diff --git a/doc/classes/PackedScene.xml b/doc/classes/PackedScene.xml
index 26d8fa8d5f..415e468e21 100644
--- a/doc/classes/PackedScene.xml
+++ b/doc/classes/PackedScene.xml
@@ -103,12 +103,6 @@
</description>
</method>
</methods>
- <members>
- <member name="_bundled" type="Dictionary" setter="_set_bundled_scene" getter="_get_bundled_scene" default="{ &quot;conn_count&quot;: 0, &quot;conns&quot;: PackedInt32Array(), &quot;editable_instances&quot;: [], &quot;names&quot;: PackedStringArray(), &quot;node_count&quot;: 0, &quot;node_paths&quot;: [], &quot;nodes&quot;: PackedInt32Array(), &quot;variants&quot;: [], &quot;version&quot;: 3 }">
- A dictionary representation of the scene contents.
- Available keys include "names" and "variants" for resources, "node_count", "nodes", "node_paths" for nodes, "editable_instances" for paths to overridden nodes, "conn_count" and "conns" for signal connections, and "version" for the format style of the PackedScene.
- </member>
- </members>
<constants>
<constant name="GEN_EDIT_STATE_DISABLED" value="0" enum="GenEditState">
If passed to [method instantiate], blocks edits to the scene state.
diff --git a/doc/classes/ParticleProcessMaterial.xml b/doc/classes/ParticleProcessMaterial.xml
index 1502690b45..28c60194c8 100644
--- a/doc/classes/ParticleProcessMaterial.xml
+++ b/doc/classes/ParticleProcessMaterial.xml
@@ -207,6 +207,10 @@
<member name="emission_ring_axis" type="Vector3" setter="set_emission_ring_axis" getter="get_emission_ring_axis">
The axis of the ring when using the emitter [constant EMISSION_SHAPE_RING].
</member>
+ <member name="emission_ring_cone_angle" type="float" setter="set_emission_ring_cone_angle" getter="get_emission_ring_cone_angle">
+ The angle of the cone when using the emitter [constant EMISSION_SHAPE_RING]. The default angle of 90 degrees results in a ring, while an angle of 0 degrees results in a cone. Intermediate values will result in a ring where one end is larger than the other.
+ [b]Note:[/b] Depending on [member emission_ring_height], the angle may be clamped if the ring's end is reached to form a perfect cone.
+ </member>
<member name="emission_ring_height" type="float" setter="set_emission_ring_height" getter="get_emission_ring_height">
The height of the ring when using the emitter [constant EMISSION_SHAPE_RING].
</member>
diff --git a/doc/classes/PortableCompressedTexture2D.xml b/doc/classes/PortableCompressedTexture2D.xml
index 3fc2aa2ab9..b85fe1de5c 100644
--- a/doc/classes/PortableCompressedTexture2D.xml
+++ b/doc/classes/PortableCompressedTexture2D.xml
@@ -52,8 +52,6 @@
</method>
</methods>
<members>
- <member name="_data" type="PackedByteArray" setter="_set_data" getter="_get_data" default="PackedByteArray()">
- </member>
<member name="keep_compressed_buffer" type="bool" setter="set_keep_compressed_buffer" getter="is_keeping_compressed_buffer" default="false">
When running on the editor, this class will keep the source compressed data in memory. Otherwise, the source compressed data is lost after loading and the resource can't be re saved.
This flag allows to keep the compressed data in memory if you intend it to persist after loading.
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index bf1632965b..1f31fef5ca 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -528,6 +528,9 @@
<member name="debug/gdscript/warnings/integer_division" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when dividing an integer by another integer (the decimal part will be discarded).
</member>
+ <member name="debug/gdscript/warnings/missing_tool" type="int" setter="" getter="" default="1">
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when the base class script has the [code]@tool[/code] annotation, but the current class script does not have it.
+ </member>
<member name="debug/gdscript/warnings/narrowing_conversion" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when passing a floating-point value to a function that expects an integer (it will be converted and lose precision).
</member>
@@ -813,9 +816,6 @@
<member name="display/window/energy_saving/keep_screen_on" type="bool" setter="" getter="" default="true">
If [code]true[/code], keeps the screen on (even in case of inactivity), so the screensaver does not take over. Works on desktop and mobile platforms.
</member>
- <member name="display/window/energy_saving/keep_screen_on.editor_hint" type="bool" setter="" getter="" default="false">
- Editor-only override for [member display/window/energy_saving/keep_screen_on]. Does not affect running project.
- </member>
<member name="display/window/handheld/orientation" type="int" setter="" getter="" default="0">
The default screen orientation to use on mobile devices. See [enum DisplayServer.ScreenOrientation] for possible values.
[b]Note:[/b] When set to a portrait orientation, this project setting does not flip the project resolution's width and height automatically. Instead, you have to set [member display/window/size/viewport_width] and [member display/window/size/viewport_height] accordingly.
@@ -2948,6 +2948,12 @@
<member name="xr/openxr/environment_blend_mode" type="int" setter="" getter="" default="&quot;0&quot;">
Specify how OpenXR should blend in the environment. This is specific to certain AR and passthrough devices where camera images are blended in by the XR compositor.
</member>
+ <member name="xr/openxr/extensions/debug_message_types" type="int" setter="" getter="" default="&quot;15&quot;">
+ Specifies the message types for which we request debug messages. Requires [member xr/openxr/extensions/debug_utils] to be set and the extension to be supported by the XR runtime.
+ </member>
+ <member name="xr/openxr/extensions/debug_utils" type="int" setter="" getter="" default="&quot;0&quot;">
+ Enables debug utilities on XR runtimes that supports the debug utils extension. Sets the maximum severity being reported (0 = disabled, 1 = error, 2 = warning, 3 = info, 4 = verbose).
+ </member>
<member name="xr/openxr/extensions/eye_gaze_interaction" type="bool" setter="" getter="" default="false">
Specify whether to enable eye tracking for this project. Depending on the platform, additional export configuration may be needed.
</member>
diff --git a/doc/classes/PropertyTweener.xml b/doc/classes/PropertyTweener.xml
index b7aa6947d9..76cf4cbfeb 100644
--- a/doc/classes/PropertyTweener.xml
+++ b/doc/classes/PropertyTweener.xml
@@ -5,6 +5,7 @@
</brief_description>
<description>
[PropertyTweener] is used to interpolate a property in an object. See [method Tween.tween_property] for more usage information.
+ The tweener will finish automatically if the target object is freed.
[b]Note:[/b] [method Tween.tween_property] is the only correct way to create [PropertyTweener]. Any [PropertyTweener] created manually will not function correctly.
</description>
<tutorials>
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index c81d5d4fab..4cdfba17e9 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -412,6 +412,14 @@
[b]Note:[/b] [param count] is unused and can be left unspecified.
</description>
</method>
+ <method name="canvas_item_attach_skeleton">
+ <return type="void" />
+ <param index="0" name="item" type="RID" />
+ <param index="1" name="skeleton" type="RID" />
+ <description>
+ Attaches a skeleton to the [CanvasItem]. Removes the previous skeleton.
+ </description>
+ </method>
<method name="canvas_item_clear">
<return type="void" />
<param index="0" name="item" type="RID" />
diff --git a/doc/classes/ResourceImporterWAV.xml b/doc/classes/ResourceImporterWAV.xml
index 8f118ace03..3caa66d262 100644
--- a/doc/classes/ResourceImporterWAV.xml
+++ b/doc/classes/ResourceImporterWAV.xml
@@ -4,17 +4,18 @@
Imports a WAV audio file for playback.
</brief_description>
<description>
- WAV is an uncompressed format, which can provide higher quality compared to Ogg Vorbis and MP3. It also has the lowest CPU cost to decode. This means high numbers of WAV sounds can be played at the same time, even on low-end deviceS.
+ WAV is an uncompressed format, which can provide higher quality compared to Ogg Vorbis and MP3. It also has the lowest CPU cost to decode. This means high numbers of WAV sounds can be played at the same time, even on low-end devices.
+ By default, Godot imports WAV files using the lossy Quite OK Audio compression. You may change this by setting the [member compress/mode] property.
</description>
<tutorials>
<link title="Importing audio samples">$DOCS_URL/tutorials/assets_pipeline/importing_audio_samples.html</link>
</tutorials>
<members>
- <member name="compress/mode" type="int" setter="" getter="" default="0">
+ <member name="compress/mode" type="int" setter="" getter="" default="2">
The compression mode to use on import.
- [b]Disabled:[/b] Imports audio data without any compression. This results in the highest possible quality.
- [b]RAM (Ima-ADPCM):[/b] Performs fast lossy compression on import. Low CPU cost, but quality is noticeably decreased compared to Ogg Vorbis or even MP3.
- [b]QOA ([url=https://qoaformat.org/]Quite OK Audio[/url]):[/b] Performs lossy compression on import. CPU cost is slightly higher than IMA-ADPCM, but quality is much higher.
+ - [b]PCM (Uncompressed):[/b] Imports audio data without any form of compression, preserving the highest possible quality. It has the lowest CPU cost, but the highest memory usage.
+ - [b]IMA ADPCM:[/b] Applies fast, lossy compression during import, noticeably decreasing the quality, but with low CPU cost and memory usage. Does not support seeking and only Forward loop mode is supported.
+ - [b][url=https://qoaformat.org/]Quite OK Audio[/url]:[/b] Also applies lossy compression on import, having a slightly higher CPU cost compared to IMA ADPCM, but much higher quality and the lowest memory usage.
</member>
<member name="edit/loop_begin" type="int" setter="" getter="" default="0">
The begin loop point to use when [member edit/loop_mode] is [b]Forward[/b], [b]Ping-Pong[/b], or [b]Backward[/b]. This is set in samples after the beginning of the audio file.
@@ -23,11 +24,12 @@
The end loop point to use when [member edit/loop_mode] is [b]Forward[/b], [b]Ping-Pong[/b], or [b]Backward[/b]. This is set in samples after the beginning of the audio file. A value of [code]-1[/code] uses the end of the audio file as the end loop point.
</member>
<member name="edit/loop_mode" type="int" setter="" getter="" default="0">
- Controls how audio should loop. This is automatically read from the WAV metadata on import.
- [b]Disabled:[/b] Don't loop audio, even if metadata indicates the file should be played back looping.
- [b]Forward:[/b] Standard audio looping.
- [b]Ping-Pong:[/b] Play audio forward until it's done playing, then play it backward and repeat. This is similar to mirrored texture repeat, but for audio.
- [b]Backward:[/b] Play audio in reverse and loop back to the end when done playing.
+ Controls how audio should loop.
+ - [b]Detect From WAV:[/b] Uses loop information from the WAV metadata.
+ - [b]Disabled:[/b] Don't loop audio, even if the metadata indicates the file playback should loop.
+ - [b]Forward:[/b] Standard audio looping. Plays the audio forward from the beginning to [member edit/loop_end], then returns to [member edit/loop_begin] and repeats.
+ - [b]Ping-Pong:[/b] Plays the audio forward until [member edit/loop_end], then backwards to [member edit/loop_begin], repeating this cycle.
+ - [b]Backward:[/b] Plays the audio backwards from [member edit/loop_end] to [member edit/loop_begin], then repeats.
[b]Note:[/b] In [AudioStreamPlayer], the [signal AudioStreamPlayer.finished] signal won't be emitted for looping audio when it reaches the end of the audio file, as the audio will keep playing indefinitely.
</member>
<member name="edit/normalize" type="bool" setter="" getter="" default="false">
diff --git a/doc/classes/ResourceLoader.xml b/doc/classes/ResourceLoader.xml
index cb0db46595..56c3208fc3 100644
--- a/doc/classes/ResourceLoader.xml
+++ b/doc/classes/ResourceLoader.xml
@@ -31,6 +31,14 @@
[b]Note:[/b] If you use [method Resource.take_over_path], this method will return [code]true[/code] for the taken path even if the resource wasn't saved (i.e. exists only in resource cache).
</description>
</method>
+ <method name="get_cached_ref">
+ <return type="Resource" />
+ <param index="0" name="path" type="String" />
+ <description>
+ Returns the cached resource reference for the given [param path].
+ [b]Note:[/b] If the resource is not cached, the returned [Resource] will be invalid.
+ </description>
+ </method>
<method name="get_dependencies">
<return type="PackedStringArray" />
<param index="0" name="path" type="String" />
diff --git a/doc/classes/ResourceUID.xml b/doc/classes/ResourceUID.xml
index 8b990b132a..24853d462d 100644
--- a/doc/classes/ResourceUID.xml
+++ b/doc/classes/ResourceUID.xml
@@ -4,7 +4,7 @@
A singleton that manages the unique identifiers of all resources within a project.
</brief_description>
<description>
- Resource UIDs (Unique IDentifiers) allow the engine to keep references between resources intact, even if files can renamed or moved. They can be accessed with [code]uid://[/code].
+ Resource UIDs (Unique IDentifiers) allow the engine to keep references between resources intact, even if files are renamed or moved. They can be accessed with [code]uid://[/code].
[ResourceUID] keeps track of all registered resource UIDs in a project, generates new UIDs, and converts between their string and integer representations.
</description>
<tutorials>
diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml
index 9a772835a6..4a2cbbc3d8 100644
--- a/doc/classes/RichTextLabel.xml
+++ b/doc/classes/RichTextLabel.xml
@@ -631,6 +631,12 @@
<member name="hint_underlined" type="bool" setter="set_hint_underline" getter="is_hint_underlined" default="true">
If [code]true[/code], the label underlines hint tags such as [code skip-lint][hint=description]{text}[/hint][/code].
</member>
+ <member name="horizontal_alignment" type="int" setter="set_horizontal_alignment" getter="get_horizontal_alignment" enum="HorizontalAlignment" default="0">
+ Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants.
+ </member>
+ <member name="justification_flags" type="int" setter="set_justification_flags" getter="get_justification_flags" enum="TextServer.JustificationFlag" is_bitfield="true" default="163">
+ Line fill alignment rules. See [enum TextServer.JustificationFlag] for more information.
+ </member>
<member name="language" type="String" setter="set_language" getter="get_language" default="&quot;&quot;">
Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead.
</member>
@@ -662,6 +668,9 @@
<member name="tab_size" type="int" setter="set_tab_size" getter="get_tab_size" default="4">
The number of spaces associated with a single tab length. Does not affect [code]\t[/code] in text tags, only indent tags.
</member>
+ <member name="tab_stops" type="PackedFloat32Array" setter="set_tab_stops" getter="get_tab_stops" default="PackedFloat32Array()">
+ Aligns text to the given tab-stops.
+ </member>
<member name="text" type="String" setter="set_text" getter="get_text" default="&quot;&quot;">
The label's text in BBCode format. Is not representative of manual modifications to the internal tag stack. Erases changes made by other methods when edited.
[b]Note:[/b] If [member bbcode_enabled] is [code]true[/code], it is unadvised to use the [code]+=[/code] operator with [member text] (e.g. [code]text += "some string"[/code]) as it replaces the whole text and can cause slowdowns. It will also erase all BBCode that was added to stack using [code]push_*[/code] methods. Use [method append_text] for adding text instead, unless you absolutely need to close a tag that was opened in an earlier method call.
diff --git a/doc/classes/ScriptEditor.xml b/doc/classes/ScriptEditor.xml
index 50adecccf5..5cf077c266 100644
--- a/doc/classes/ScriptEditor.xml
+++ b/doc/classes/ScriptEditor.xml
@@ -10,6 +10,12 @@
<tutorials>
</tutorials>
<methods>
+ <method name="get_breakpoints">
+ <return type="PackedStringArray" />
+ <description>
+ Returns array of breakpoints.
+ </description>
+ </method>
<method name="get_current_editor" qualifiers="const">
<return type="ScriptEditorBase" />
<description>
diff --git a/doc/classes/Semaphore.xml b/doc/classes/Semaphore.xml
index d0db24dfb7..3ecb5c23af 100644
--- a/doc/classes/Semaphore.xml
+++ b/doc/classes/Semaphore.xml
@@ -17,8 +17,9 @@
<methods>
<method name="post">
<return type="void" />
+ <param index="0" name="count" type="int" default="1" />
<description>
- Lowers the [Semaphore], allowing one more thread in.
+ Lowers the [Semaphore], allowing one thread in, or more if [param count] is specified.
</description>
</method>
<method name="try_wait">
diff --git a/doc/classes/Shader.xml b/doc/classes/Shader.xml
index b71f9ca1b0..68176dea14 100644
--- a/doc/classes/Shader.xml
+++ b/doc/classes/Shader.xml
@@ -12,7 +12,7 @@
</tutorials>
<methods>
<method name="get_default_texture_parameter" qualifiers="const">
- <return type="Texture2D" />
+ <return type="Texture" />
<param index="0" name="name" type="StringName" />
<param index="1" name="index" type="int" default="0" />
<description>
@@ -38,7 +38,7 @@
<method name="set_default_texture_parameter">
<return type="void" />
<param index="0" name="name" type="StringName" />
- <param index="1" name="texture" type="Texture2D" />
+ <param index="1" name="texture" type="Texture" />
<param index="2" name="index" type="int" default="0" />
<description>
Sets the default texture to be used with a texture uniform. The default is used if a texture is not set in the [ShaderMaterial].
diff --git a/doc/classes/SpinBox.xml b/doc/classes/SpinBox.xml
index 2fe2a0eaa1..3fb30a81b8 100644
--- a/doc/classes/SpinBox.xml
+++ b/doc/classes/SpinBox.xml
@@ -24,7 +24,7 @@
[/codeblocks]
See [Range] class for more options over the [SpinBox].
[b]Note:[/b] With the [SpinBox]'s context menu disabled, you can right-click the bottom half of the spinbox to set the value to its minimum, while right-clicking the top half sets the value to its maximum.
- [b]Note:[/b] [SpinBox] relies on an underlying [LineEdit] node. To theme a [SpinBox]'s background, add theme items for [LineEdit] and customize them.
+ [b]Note:[/b] [SpinBox] relies on an underlying [LineEdit] node. To theme a [SpinBox]'s background, add theme items for [LineEdit] and customize them. The [LineEdit] has the [code]SpinBoxInnerLineEdit[/code] theme variation, so that you can give it a distinct appearance from regular [LineEdit]s.
[b]Note:[/b] If you want to implement drag and drop for the underlying [LineEdit], you can use [method Control.set_drag_forwarding] on the node returned by [method get_line_edit].
</description>
<tutorials>
@@ -70,8 +70,98 @@
</member>
</members>
<theme_items>
+ <theme_item name="down_disabled_icon_modulate" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)">
+ Down button icon modulation color, when the button is disabled.
+ </theme_item>
+ <theme_item name="down_hover_icon_modulate" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)">
+ Down button icon modulation color, when the button is hovered.
+ </theme_item>
+ <theme_item name="down_icon_modulate" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)">
+ Down button icon modulation color.
+ </theme_item>
+ <theme_item name="down_pressed_icon_modulate" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)">
+ Down button icon modulation color, when the button is being pressed.
+ </theme_item>
+ <theme_item name="up_disabled_icon_modulate" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)">
+ Up button icon modulation color, when the button is disabled.
+ </theme_item>
+ <theme_item name="up_hover_icon_modulate" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)">
+ Up button icon modulation color, when the button is hovered.
+ </theme_item>
+ <theme_item name="up_icon_modulate" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)">
+ Up button icon modulation color.
+ </theme_item>
+ <theme_item name="up_pressed_icon_modulate" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)">
+ Up button icon modulation color, when the button is being pressed.
+ </theme_item>
+ <theme_item name="buttons_vertical_separation" data_type="constant" type="int" default="0">
+ Vertical separation between the up and down buttons.
+ </theme_item>
+ <theme_item name="buttons_width" data_type="constant" type="int" default="16">
+ Width of the up and down buttons. If smaller than any icon set on the buttons, the respective icon may overlap neighboring elements, unless [theme_item set_min_buttons_width_from_icons] is different than [code]0[/code].
+ </theme_item>
+ <theme_item name="field_and_buttons_separation" data_type="constant" type="int" default="2">
+ Width of the horizontal separation between the text input field ([LineEdit]) and the buttons.
+ </theme_item>
+ <theme_item name="set_min_buttons_width_from_icons" data_type="constant" type="int" default="1">
+ If not [code]0[/code], the minimum button width corresponds to the widest of all icons set on those buttons, even if [theme_item buttons_width] is smaller.
+ </theme_item>
+ <theme_item name="down" data_type="icon" type="Texture2D">
+ Down button icon, displayed in the middle of the down (value-decreasing) button.
+ </theme_item>
+ <theme_item name="down_disabled" data_type="icon" type="Texture2D">
+ Down button icon when the button is disabled.
+ </theme_item>
+ <theme_item name="down_hover" data_type="icon" type="Texture2D">
+ Down button icon when the button is hovered.
+ </theme_item>
+ <theme_item name="down_pressed" data_type="icon" type="Texture2D">
+ Down button icon when the button is being pressed.
+ </theme_item>
+ <theme_item name="up" data_type="icon" type="Texture2D">
+ Up button icon, displayed in the middle of the up (value-increasing) button.
+ </theme_item>
+ <theme_item name="up_disabled" data_type="icon" type="Texture2D">
+ Up button icon when the button is disabled.
+ </theme_item>
+ <theme_item name="up_hover" data_type="icon" type="Texture2D">
+ Up button icon when the button is hovered.
+ </theme_item>
+ <theme_item name="up_pressed" data_type="icon" type="Texture2D">
+ Up button icon when the button is being pressed.
+ </theme_item>
<theme_item name="updown" data_type="icon" type="Texture2D">
- Sets a custom [Texture2D] for up and down arrows of the [SpinBox].
+ Single texture representing both the up and down buttons icons. It is displayed in the middle of the buttons and does not change upon interaction. It is recommended to use individual [theme_item up] and [theme_item down] graphics for better usability. This can also be used as additional decoration between the two buttons.
+ </theme_item>
+ <theme_item name="down_background" data_type="style" type="StyleBox">
+ Background style of the down button.
+ </theme_item>
+ <theme_item name="down_background_disabled" data_type="style" type="StyleBox">
+ Background style of the down button when disabled.
+ </theme_item>
+ <theme_item name="down_background_hovered" data_type="style" type="StyleBox">
+ Background style of the down button when hovered.
+ </theme_item>
+ <theme_item name="down_background_pressed" data_type="style" type="StyleBox">
+ Background style of the down button when being pressed.
+ </theme_item>
+ <theme_item name="field_and_buttons_separator" data_type="style" type="StyleBox">
+ [StyleBox] drawn in the space occupied by the separation between the input field and the buttons.
+ </theme_item>
+ <theme_item name="up_background" data_type="style" type="StyleBox">
+ Background style of the up button.
+ </theme_item>
+ <theme_item name="up_background_disabled" data_type="style" type="StyleBox">
+ Background style of the up button when disabled.
+ </theme_item>
+ <theme_item name="up_background_hovered" data_type="style" type="StyleBox">
+ Background style of the up button when hovered.
+ </theme_item>
+ <theme_item name="up_background_pressed" data_type="style" type="StyleBox">
+ Background style of the up button when being pressed.
+ </theme_item>
+ <theme_item name="up_down_buttons_separator" data_type="style" type="StyleBox">
+ [StyleBox] drawn in the space occupied by the separation between the up and down buttons.
</theme_item>
</theme_items>
</class>
diff --git a/doc/classes/String.xml b/doc/classes/String.xml
index de3d3e7cb9..d99eaa64a6 100644
--- a/doc/classes/String.xml
+++ b/doc/classes/String.xml
@@ -451,6 +451,19 @@
Returns [code]true[/code] if all characters of this string can be found in [param text] in their original order, [b]ignoring case[/b].
</description>
</method>
+ <method name="is_valid_ascii_identifier" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if this string is a valid ASCII identifier. A valid ASCII identifier may contain only letters, digits, and underscores ([code]_[/code]), and the first character may not be a digit.
+ [codeblock]
+ print("node_2d".is_valid_ascii_identifier()) # Prints true
+ print("TYPE_FLOAT".is_valid_ascii_identifier()) # Prints true
+ print("1st_method".is_valid_ascii_identifier()) # Prints false
+ print("MyMethod#2".is_valid_ascii_identifier()) # Prints false
+ [/codeblock]
+ See also [method is_valid_unicode_identifier].
+ </description>
+ </method>
<method name="is_valid_filename" qualifiers="const">
<return type="bool" />
<description>
@@ -490,7 +503,7 @@
Returns [code]true[/code] if this string is a valid color in hexadecimal HTML notation. The string must be a hexadecimal value (see [method is_valid_hex_number]) of either 3, 4, 6 or 8 digits, and may be prefixed by a hash sign ([code]#[/code]). Other HTML notations for colors, such as names or [code]hsl()[/code], are not considered valid. See also [method Color.html].
</description>
</method>
- <method name="is_valid_identifier" qualifiers="const">
+ <method name="is_valid_identifier" qualifiers="const" deprecated="Use [method is_valid_ascii_identifier] instead.">
<return type="bool" />
<description>
Returns [code]true[/code] if this string is a valid identifier. A valid identifier may contain only letters, digits and underscores ([code]_[/code]), and the first character may not be a digit.
@@ -521,6 +534,23 @@
Returns [code]true[/code] if this string represents a well-formatted IPv4 or IPv6 address. This method considers [url=https://en.wikipedia.org/wiki/Reserved_IP_addresses]reserved IP addresses[/url] such as [code]"0.0.0.0"[/code] and [code]"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"[/code] as valid.
</description>
</method>
+ <method name="is_valid_unicode_identifier" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if this string is a valid Unicode identifier.
+ A valid Unicode identifier must begin with a Unicode character of class [code]XID_Start[/code] or [code]"_"[/code], and may contain Unicode characters of class [code]XID_Continue[/code] in the other positions.
+ [codeblock]
+ print("node_2d".is_valid_unicode_identifier()) # Prints true
+ print("1st_method".is_valid_unicode_identifier()) # Prints false
+ print("MyMethod#2".is_valid_unicode_identifier()) # Prints false
+ print("állóképesség".is_valid_unicode_identifier()) # Prints true
+ print("выносливость".is_valid_unicode_identifier()) # Prints true
+ print("体力".is_valid_unicode_identifier()) # Prints true
+ [/codeblock]
+ See also [method is_valid_ascii_identifier].
+ [b]Note:[/b] This method checks identifiers the same way as GDScript. See [method TextServer.is_valid_identifier] for more advanced checks.
+ </description>
+ </method>
<method name="join" qualifiers="const">
<return type="String" />
<param index="0" name="parts" type="PackedStringArray" />
diff --git a/doc/classes/StringName.xml b/doc/classes/StringName.xml
index 836ca6b4ba..1a891de05f 100644
--- a/doc/classes/StringName.xml
+++ b/doc/classes/StringName.xml
@@ -420,6 +420,19 @@
Returns [code]true[/code] if all characters of this string can be found in [param text] in their original order, [b]ignoring case[/b].
</description>
</method>
+ <method name="is_valid_ascii_identifier" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if this string is a valid ASCII identifier. A valid ASCII identifier may contain only letters, digits, and underscores ([code]_[/code]), and the first character may not be a digit.
+ [codeblock]
+ print("node_2d".is_valid_ascii_identifier()) # Prints true
+ print("TYPE_FLOAT".is_valid_ascii_identifier()) # Prints true
+ print("1st_method".is_valid_ascii_identifier()) # Prints false
+ print("MyMethod#2".is_valid_ascii_identifier()) # Prints false
+ [/codeblock]
+ See also [method is_valid_unicode_identifier].
+ </description>
+ </method>
<method name="is_valid_filename" qualifiers="const">
<return type="bool" />
<description>
@@ -459,7 +472,7 @@
Returns [code]true[/code] if this string is a valid color in hexadecimal HTML notation. The string must be a hexadecimal value (see [method is_valid_hex_number]) of either 3, 4, 6 or 8 digits, and may be prefixed by a hash sign ([code]#[/code]). Other HTML notations for colors, such as names or [code]hsl()[/code], are not considered valid. See also [method Color.html].
</description>
</method>
- <method name="is_valid_identifier" qualifiers="const">
+ <method name="is_valid_identifier" qualifiers="const" deprecated="Use [method is_valid_ascii_identifier] instead.">
<return type="bool" />
<description>
Returns [code]true[/code] if this string is a valid identifier. A valid identifier may contain only letters, digits and underscores ([code]_[/code]), and the first character may not be a digit.
@@ -490,6 +503,23 @@
Returns [code]true[/code] if this string represents a well-formatted IPv4 or IPv6 address. This method considers [url=https://en.wikipedia.org/wiki/Reserved_IP_addresses]reserved IP addresses[/url] such as [code]"0.0.0.0"[/code] and [code]"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"[/code] as valid.
</description>
</method>
+ <method name="is_valid_unicode_identifier" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if this string is a valid Unicode identifier.
+ A valid Unicode identifier must begin with a Unicode character of class [code]XID_Start[/code] or [code]"_"[/code], and may contain Unicode characters of class [code]XID_Continue[/code] in the other positions.
+ [codeblock]
+ print("node_2d".is_valid_unicode_identifier()) # Prints true
+ print("1st_method".is_valid_unicode_identifier()) # Prints false
+ print("MyMethod#2".is_valid_unicode_identifier()) # Prints false
+ print("állóképesség".is_valid_unicode_identifier()) # Prints true
+ print("выносливость".is_valid_unicode_identifier()) # Prints true
+ print("体力".is_valid_unicode_identifier()) # Prints true
+ [/codeblock]
+ See also [method is_valid_ascii_identifier].
+ [b]Note:[/b] This method checks identifiers the same way as GDScript. See [method TextServer.is_valid_identifier] for more advanced checks.
+ </description>
+ </method>
<method name="join" qualifiers="const">
<return type="String" />
<param index="0" name="parts" type="PackedStringArray" />
diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml
index f71601f9ae..6505e48fb9 100644
--- a/doc/classes/TextEdit.xml
+++ b/doc/classes/TextEdit.xml
@@ -1327,7 +1327,10 @@
Text shown when the [TextEdit] is empty. It is [b]not[/b] the [TextEdit]'s default value (see [member text]).
</member>
<member name="scroll_fit_content_height" type="bool" setter="set_fit_content_height_enabled" getter="is_fit_content_height_enabled" default="false">
- If [code]true[/code], [TextEdit] will disable vertical scroll and fit minimum height to the number of visible lines.
+ If [code]true[/code], [TextEdit] will disable vertical scroll and fit minimum height to the number of visible lines. When both this property and [member scroll_fit_content_width] are [code]true[/code], no scrollbars will be displayed.
+ </member>
+ <member name="scroll_fit_content_width" type="bool" setter="set_fit_content_width_enabled" getter="is_fit_content_width_enabled" default="false">
+ If [code]true[/code], [TextEdit] will disable horizontal scroll and fit minimum width to the widest line in the text. When both this property and [member scroll_fit_content_height] are [code]true[/code], no scrollbars will be displayed.
</member>
<member name="scroll_horizontal" type="int" setter="set_h_scroll" getter="get_h_scroll" default="0">
If there is a horizontal scrollbar, this determines the current horizontal scroll value in pixels.
diff --git a/doc/classes/TextMesh.xml b/doc/classes/TextMesh.xml
index 9e705311c5..898d19aed3 100644
--- a/doc/classes/TextMesh.xml
+++ b/doc/classes/TextMesh.xml
@@ -31,7 +31,7 @@
Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants.
</member>
<member name="justification_flags" type="int" setter="set_justification_flags" getter="get_justification_flags" enum="TextServer.JustificationFlag" is_bitfield="true" default="163">
- Line fill alignment rules. For more info see [enum TextServer.JustificationFlag].
+ Line fill alignment rules. See [enum TextServer.JustificationFlag] for more information.
</member>
<member name="language" type="String" setter="set_language" getter="get_language" default="&quot;&quot;">
Language code used for text shaping algorithms, if left empty current locale is used instead.
diff --git a/doc/classes/TextParagraph.xml b/doc/classes/TextParagraph.xml
index c6511a2b8e..46197f19b8 100644
--- a/doc/classes/TextParagraph.xml
+++ b/doc/classes/TextParagraph.xml
@@ -278,7 +278,7 @@
Ellipsis character used for text clipping.
</member>
<member name="justification_flags" type="int" setter="set_justification_flags" getter="get_justification_flags" enum="TextServer.JustificationFlag" is_bitfield="true" default="163">
- Line fill alignment rules. For more info see [enum TextServer.JustificationFlag].
+ Line fill alignment rules. See [enum TextServer.JustificationFlag] for more information.
</member>
<member name="max_lines_visible" type="int" setter="set_max_lines_visible" getter="get_max_lines_visible" default="-1">
Limits the lines of text shown.
diff --git a/doc/classes/TileData.xml b/doc/classes/TileData.xml
index 91df90580c..9468763b6e 100644
--- a/doc/classes/TileData.xml
+++ b/doc/classes/TileData.xml
@@ -134,7 +134,7 @@
<param index="1" name="polygon_index" type="int" />
<param index="2" name="one_way_margin" type="float" />
<description>
- Enables/disables one-way collisions on the polygon at index [param polygon_index] for TileSet physics layer with index [param layer_id].
+ Sets the one-way margin (for one-way platforms) of the polygon at index [param polygon_index] for TileSet physics layer with index [param layer_id].
</description>
</method>
<method name="set_collision_polygon_points">
diff --git a/doc/classes/TileMapLayer.xml b/doc/classes/TileMapLayer.xml
index bead1c32c0..135f85de69 100644
--- a/doc/classes/TileMapLayer.xml
+++ b/doc/classes/TileMapLayer.xml
@@ -284,6 +284,9 @@
<member name="navigation_visibility_mode" type="int" setter="set_navigation_visibility_mode" getter="get_navigation_visibility_mode" enum="TileMapLayer.DebugVisibilityMode" default="0">
Show or hide the [TileMapLayer]'s navigation meshes. If set to [constant DEBUG_VISIBILITY_MODE_DEFAULT], this depends on the show navigation debug settings.
</member>
+ <member name="occlusion_enabled" type="bool" setter="set_occlusion_enabled" getter="is_occlusion_enabled" default="true">
+ Enable or disable light occlusion.
+ </member>
<member name="rendering_quadrant_size" type="int" setter="set_rendering_quadrant_size" getter="get_rendering_quadrant_size" default="16">
The [TileMapLayer]'s quadrant size. A quadrant is a group of tiles to be drawn together on a single canvas item, for optimization purposes. [member rendering_quadrant_size] defines the length of a square's side, in the map's coordinate system, that forms the quadrant. Thus, the default quadrant size groups together [code]16 * 16 = 256[/code] tiles.
The quadrant size does not apply on a Y-sorted [TileMapLayer], as tiles are grouped by Y position instead in that case.
diff --git a/doc/classes/Tweener.xml b/doc/classes/Tweener.xml
index 65148e875d..88f5f9978c 100644
--- a/doc/classes/Tweener.xml
+++ b/doc/classes/Tweener.xml
@@ -11,7 +11,7 @@
<signals>
<signal name="finished">
<description>
- Emitted when the [Tweener] has just finished its job.
+ Emitted when the [Tweener] has just finished its job or became invalid (e.g. due to a freed object).
</description>
</signal>
</signals>
diff --git a/doc/classes/VehicleWheel3D.xml b/doc/classes/VehicleWheel3D.xml
index 92b2297bf4..7b4952580c 100644
--- a/doc/classes/VehicleWheel3D.xml
+++ b/doc/classes/VehicleWheel3D.xml
@@ -18,6 +18,18 @@
Returns [code]null[/code] if the wheel is not in contact with a surface, or the contact body is not a [PhysicsBody3D].
</description>
</method>
+ <method name="get_contact_normal" qualifiers="const">
+ <return type="Vector3" />
+ <description>
+ Returns the normal of the suspension's collision in world space if the wheel is in contact. If the wheel isn't in contact with anything, returns a vector pointing directly along the suspension axis toward the vehicle in world space.
+ </description>
+ </method>
+ <method name="get_contact_point" qualifiers="const">
+ <return type="Vector3" />
+ <description>
+ Returns the point of the suspension's collision in world space if the wheel is in contact. If the wheel isn't in contact with anything, returns the maximum point of the wheel's ray cast in world space, which is defined by [code]wheel_rest_length + wheel_radius[/code].
+ </description>
+ </method>
<method name="get_rpm" qualifiers="const">
<return type="float" />
<description>
diff --git a/doc/classes/VisualShaderNodeCubemap.xml b/doc/classes/VisualShaderNodeCubemap.xml
index 4e6cf2120a..6f3df9865a 100644
--- a/doc/classes/VisualShaderNodeCubemap.xml
+++ b/doc/classes/VisualShaderNodeCubemap.xml
@@ -9,7 +9,7 @@
<tutorials>
</tutorials>
<members>
- <member name="cube_map" type="Cubemap" setter="set_cube_map" getter="get_cube_map">
+ <member name="cube_map" type="TextureLayered" setter="set_cube_map" getter="get_cube_map">
The [Cubemap] texture to sample when using [constant SOURCE_TEXTURE] as [member source].
</member>
<member name="source" type="int" setter="set_source" getter="get_source" enum="VisualShaderNodeCubemap.Source" default="0">
diff --git a/doc/classes/VisualShaderNodeTexture2DArray.xml b/doc/classes/VisualShaderNodeTexture2DArray.xml
index 8852cb7cb4..bdf5a42821 100644
--- a/doc/classes/VisualShaderNodeTexture2DArray.xml
+++ b/doc/classes/VisualShaderNodeTexture2DArray.xml
@@ -9,7 +9,7 @@
<tutorials>
</tutorials>
<members>
- <member name="texture_array" type="Texture2DArray" setter="set_texture_array" getter="get_texture_array">
+ <member name="texture_array" type="TextureLayered" setter="set_texture_array" getter="get_texture_array">
A source texture array. Used if [member VisualShaderNodeSample3D.source] is set to [constant VisualShaderNodeSample3D.SOURCE_TEXTURE].
</member>
</members>
diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl
index ce2db7fa85..6143ce2167 100644
--- a/drivers/gles3/shaders/scene.glsl
+++ b/drivers/gles3/shaders/scene.glsl
@@ -44,6 +44,7 @@ LIGHTMAP_BICUBIC_FILTER = false
#define M_PI 3.14159265359
#define SHADER_IS_SRGB true
+#define SHADER_SPACE_FAR -1.0
#include "stdlib_inc.glsl"
@@ -583,6 +584,7 @@ void main() {
/* clang-format on */
#define SHADER_IS_SRGB true
+#define SHADER_SPACE_FAR -1.0
#define FLAGS_NON_UNIFORM_SCALE (1 << 4)
diff --git a/drivers/gles3/shaders/stdlib_inc.glsl b/drivers/gles3/shaders/stdlib_inc.glsl
index 029084c34c..f88c218506 100644
--- a/drivers/gles3/shaders/stdlib_inc.glsl
+++ b/drivers/gles3/shaders/stdlib_inc.glsl
@@ -9,19 +9,17 @@
// Floating point pack/unpack functions are part of the GLSL ES 300 specification used by web and mobile.
uint float2half(uint f) {
- uint e = f & uint(0x7f800000);
- if (e <= uint(0x38000000)) {
- return uint(0);
- } else {
- return ((f >> uint(16)) & uint(0x8000)) |
- (((e - uint(0x38000000)) >> uint(13)) & uint(0x7c00)) |
- ((f >> uint(13)) & uint(0x03ff));
- }
+ uint b = f + uint(0x00001000);
+ uint e = (b & uint(0x7F800000)) >> 23;
+ uint m = b & uint(0x007FFFFF);
+ return (b & uint(0x80000000)) >> uint(16) | uint(e > uint(112)) * ((((e - uint(112)) << uint(10)) & uint(0x7C00)) | m >> uint(13)) | (uint(e < uint(113)) & uint(e > uint(101))) * ((((uint(0x007FF000) + m) >> (uint(125) - e)) + uint(1)) >> uint(1)) | uint(e > uint(143)) * uint(0x7FFF);
}
uint half2float(uint h) {
- uint h_e = h & uint(0x7c00);
- return ((h & uint(0x8000)) << uint(16)) | uint((h_e >> uint(10)) != uint(0)) * (((h_e + uint(0x1c000)) << uint(13)) | ((h & uint(0x03ff)) << uint(13)));
+ uint e = (h & uint(0x7C00)) >> uint(10);
+ uint m = (h & uint(0x03FF)) << uint(13);
+ uint v = m >> uint(23);
+ return (h & uint(0x8000)) << uint(16) | uint(e != uint(0)) * ((e + uint(112)) << uint(23) | m) | (uint(e == uint(0)) & uint(m != uint(0))) * ((v - uint(37)) << uint(23) | ((m << (uint(150) - v)) & uint(0x007FE000)));
}
uint godot_packHalf2x16(vec2 v) {
diff --git a/drivers/gles3/storage/light_storage.h b/drivers/gles3/storage/light_storage.h
index 81e7220439..ed00dd235f 100644
--- a/drivers/gles3/storage/light_storage.h
+++ b/drivers/gles3/storage/light_storage.h
@@ -203,7 +203,7 @@ struct LightmapInstance {
class LightStorage : public RendererLightStorage {
public:
- enum ShadowAtlastQuadrant {
+ enum ShadowAtlastQuadrant : uint32_t {
QUADRANT_SHIFT = 27,
OMNI_LIGHT_FLAG = 1 << 26,
SHADOW_INDEX_MASK = OMNI_LIGHT_FLAG - 1,
diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp
index 25af7ff691..a37eba3b15 100644
--- a/drivers/gles3/storage/material_storage.cpp
+++ b/drivers/gles3/storage/material_storage.cpp
@@ -1273,6 +1273,7 @@ MaterialStorage::MaterialStorage() {
actions.renames["CUSTOM2"] = "custom2_attrib";
actions.renames["CUSTOM3"] = "custom3_attrib";
actions.renames["OUTPUT_IS_SRGB"] = "SHADER_IS_SRGB";
+ actions.renames["CLIP_SPACE_FAR"] = "SHADER_SPACE_FAR";
actions.renames["LIGHT_VERTEX"] = "light_vertex";
actions.renames["NODE_POSITION_WORLD"] = "model_matrix[3].xyz";
diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp
index 57fe96fb6f..36393dde86 100644
--- a/drivers/gles3/storage/texture_storage.cpp
+++ b/drivers/gles3/storage/texture_storage.cpp
@@ -1389,8 +1389,22 @@ void TextureStorage::texture_debug_usage(List<RS::TextureInfo> *r_info) {
tinfo.format = t->format;
tinfo.width = t->alloc_width;
tinfo.height = t->alloc_height;
- tinfo.depth = t->depth;
tinfo.bytes = t->total_data_size;
+
+ switch (t->type) {
+ case Texture::TYPE_3D:
+ tinfo.depth = t->depth;
+ break;
+
+ case Texture::TYPE_LAYERED:
+ tinfo.depth = t->layers;
+ break;
+
+ default:
+ tinfo.depth = 0;
+ break;
+ }
+
r_info->push_back(tinfo);
}
}
@@ -1521,7 +1535,11 @@ void TextureStorage::_texture_set_data(RID p_texture, const Ref<Image> &p_image,
h = MAX(1, h >> 1);
}
- texture->total_data_size = tsize;
+ if (texture->target == GL_TEXTURE_CUBE_MAP || texture->target == GL_TEXTURE_2D_ARRAY) {
+ texture->total_data_size = tsize * texture->layers;
+ } else {
+ texture->total_data_size = tsize;
+ }
texture->stored_cube_sides |= (1 << p_layer);
diff --git a/drivers/metal/metal_objects.h b/drivers/metal/metal_objects.h
index f0a3e85f88..97f33bb1e8 100644
--- a/drivers/metal/metal_objects.h
+++ b/drivers/metal/metal_objects.h
@@ -227,6 +227,7 @@ public:
id<MTLRenderCommandEncoder> encoder = nil;
id<MTLBuffer> __unsafe_unretained index_buffer = nil; // Buffer is owned by RDD.
MTLIndexType index_type = MTLIndexTypeUInt16;
+ uint32_t index_offset = 0;
LocalVector<id<MTLBuffer> __unsafe_unretained> vertex_buffers;
LocalVector<NSUInteger> vertex_offsets;
// clang-format off
diff --git a/drivers/metal/metal_objects.mm b/drivers/metal/metal_objects.mm
index c66b683e23..abdcccf00c 100644
--- a/drivers/metal/metal_objects.mm
+++ b/drivers/metal/metal_objects.mm
@@ -717,6 +717,7 @@ void MDCommandBuffer::render_bind_index_buffer(RDD::BufferID p_buffer, RDD::Inde
render.index_buffer = rid::get(p_buffer);
render.index_type = p_format == RDD::IndexBufferFormat::INDEX_BUFFER_FORMAT_UINT16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32;
+ render.index_offset = p_offset;
}
void MDCommandBuffer::render_draw_indexed(uint32_t p_index_count,
@@ -729,13 +730,16 @@ void MDCommandBuffer::render_draw_indexed(uint32_t p_index_count,
id<MTLRenderCommandEncoder> enc = render.encoder;
+ uint32_t index_offset = render.index_offset;
+ index_offset += p_first_index * (render.index_type == MTLIndexTypeUInt16 ? sizeof(uint16_t) : sizeof(uint32_t));
+
[enc drawIndexedPrimitives:render.pipeline->raster_state.render_primitive
indexCount:p_index_count
indexType:render.index_type
indexBuffer:render.index_buffer
- indexBufferOffset:p_vertex_offset
+ indexBufferOffset:index_offset
instanceCount:p_instance_count
- baseVertex:p_first_index
+ baseVertex:p_vertex_offset
baseInstance:p_first_instance];
}
diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm
index 0d88a14fbc..9d691a0d23 100644
--- a/drivers/metal/rendering_device_driver_metal.mm
+++ b/drivers/metal/rendering_device_driver_metal.mm
@@ -3112,7 +3112,7 @@ RenderingDeviceDriverMetal::Result<id<MTLFunction>> RenderingDeviceDriverMetal::
NSArray<MTLFunctionConstant *> *constants = function.functionConstantsDictionary.allValues;
bool is_sorted = true;
for (uint32_t i = 1; i < constants.count; i++) {
- if (constants[i - 1].index < constants[i].index) {
+ if (constants[i - 1].index > constants[i].index) {
is_sorted = false;
break;
}
diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp
index ea8b42b2e4..32f2d7dd79 100644
--- a/drivers/unix/file_access_unix.cpp
+++ b/drivers/unix/file_access_unix.cpp
@@ -218,67 +218,13 @@ bool FileAccessUnix::eof_reached() const {
return last_error == ERR_FILE_EOF;
}
-uint8_t FileAccessUnix::get_8() const {
- ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");
- uint8_t b;
- if (fread(&b, 1, 1, f) == 0) {
- check_errors();
- b = '\0';
- }
- return b;
-}
-
-uint16_t FileAccessUnix::get_16() const {
- ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");
-
- uint16_t b = 0;
- if (fread(&b, 1, 2, f) != 2) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP16(b);
- }
-
- return b;
-}
-
-uint32_t FileAccessUnix::get_32() const {
- ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");
-
- uint32_t b = 0;
- if (fread(&b, 1, 4, f) != 4) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP32(b);
- }
-
- return b;
-}
-
-uint64_t FileAccessUnix::get_64() const {
- ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");
-
- uint64_t b = 0;
- if (fread(&b, 1, 8, f) != 8) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP64(b);
- }
-
- return b;
-}
-
uint64_t FileAccessUnix::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_NULL_V_MSG(f, -1, "File must be opened before use.");
+ ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
uint64_t read = fread(p_dst, 1, p_length, f);
check_errors();
+
return read;
}
@@ -308,41 +254,6 @@ void FileAccessUnix::flush() {
fflush(f);
}
-void FileAccessUnix::store_8(uint8_t p_dest) {
- ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
- ERR_FAIL_COND(fwrite(&p_dest, 1, 1, f) != 1);
-}
-
-void FileAccessUnix::store_16(uint16_t p_dest) {
- ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
-
- if (big_endian) {
- p_dest = BSWAP16(p_dest);
- }
-
- ERR_FAIL_COND(fwrite(&p_dest, 1, 2, f) != 2);
-}
-
-void FileAccessUnix::store_32(uint32_t p_dest) {
- ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
-
- if (big_endian) {
- p_dest = BSWAP32(p_dest);
- }
-
- ERR_FAIL_COND(fwrite(&p_dest, 1, 4, f) != 4);
-}
-
-void FileAccessUnix::store_64(uint64_t p_dest) {
- ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
-
- if (big_endian) {
- p_dest = BSWAP64(p_dest);
- }
-
- ERR_FAIL_COND(fwrite(&p_dest, 1, 8, f) != 8);
-}
-
void FileAccessUnix::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
ERR_FAIL_COND(!p_src && p_length > 0);
diff --git a/drivers/unix/file_access_unix.h b/drivers/unix/file_access_unix.h
index c0286dbff3..76f629f7c2 100644
--- a/drivers/unix/file_access_unix.h
+++ b/drivers/unix/file_access_unix.h
@@ -67,20 +67,12 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
- virtual uint16_t get_16() const override;
- virtual uint32_t get_32() const override;
- virtual uint64_t get_64() const override;
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override;
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
- virtual void store_16(uint16_t p_dest) override;
- virtual void store_32(uint32_t p_dest) override;
- virtual void store_64(uint64_t p_dest) override;
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_path) override; ///< return true if a file exists
diff --git a/drivers/unix/file_access_unix_pipe.cpp b/drivers/unix/file_access_unix_pipe.cpp
index 5d9a27ad05..34758e8c7d 100644
--- a/drivers/unix/file_access_unix_pipe.cpp
+++ b/drivers/unix/file_access_unix_pipe.cpp
@@ -125,22 +125,9 @@ String FileAccessUnixPipe::get_path_absolute() const {
return path_src;
}
-uint8_t FileAccessUnixPipe::get_8() const {
- ERR_FAIL_COND_V_MSG(fd[0] < 0, 0, "Pipe must be opened before use.");
-
- uint8_t b;
- if (::read(fd[0], &b, 1) == 0) {
- last_error = ERR_FILE_CANT_READ;
- b = '\0';
- } else {
- last_error = OK;
- }
- return b;
-}
-
uint64_t FileAccessUnixPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
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) {
@@ -155,18 +142,10 @@ Error FileAccessUnixPipe::get_error() const {
return last_error;
}
-void FileAccessUnixPipe::store_8(uint8_t p_src) {
- ERR_FAIL_COND_MSG(fd[1] < 0, "Pipe must be opened before use.");
- if (::write(fd[1], &p_src, 1) != 1) {
- last_error = ERR_FILE_CANT_WRITE;
- } else {
- last_error = OK;
- }
-}
-
void FileAccessUnixPipe::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND_MSG(fd[1] < 0, "Pipe must be opened before use.");
ERR_FAIL_COND(!p_src && p_length > 0);
+
if (::write(fd[1], p_src, p_length) != (ssize_t)p_length) {
last_error = ERR_FILE_CANT_WRITE;
} else {
diff --git a/drivers/unix/file_access_unix_pipe.h b/drivers/unix/file_access_unix_pipe.h
index 8e7988791b..19acdb5a37 100644
--- a/drivers/unix/file_access_unix_pipe.h
+++ b/drivers/unix/file_access_unix_pipe.h
@@ -65,14 +65,12 @@ public:
virtual bool eof_reached() const override { return false; }
- virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override {}
- virtual void store_8(uint8_t p_src) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_path) override { return false; }
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp
index 2ba353868b..4ea46e8214 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp
@@ -497,6 +497,7 @@ Error RenderingDeviceDriverVulkan::_initialize_device_extensions() {
_register_requested_device_extension(VK_KHR_MAINTENANCE_2_EXTENSION_NAME, false);
_register_requested_device_extension(VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_EXTENSION_NAME, false);
_register_requested_device_extension(VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME, false);
+ _register_requested_device_extension(VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME, false);
if (Engine::get_singleton()->is_generate_spirv_debug_info_enabled()) {
_register_requested_device_extension(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME, true);
@@ -1705,6 +1706,16 @@ RDD::TextureID RenderingDeviceDriverVulkan::texture_create(const TextureFormat &
image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
}
+ VkImageViewASTCDecodeModeEXT decode_mode;
+ if (enabled_device_extension_names.has(VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME)) {
+ if (image_view_create_info.format >= VK_FORMAT_ASTC_4x4_UNORM_BLOCK && image_view_create_info.format <= VK_FORMAT_ASTC_12x12_SRGB_BLOCK) {
+ decode_mode.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_ASTC_DECODE_MODE_EXT;
+ decode_mode.pNext = nullptr;
+ decode_mode.decodeMode = VK_FORMAT_R8G8B8A8_UNORM;
+ image_view_create_info.pNext = &decode_mode;
+ }
+ }
+
VkImageView vk_image_view = VK_NULL_HANDLE;
err = vkCreateImageView(vk_device, &image_view_create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_IMAGE_VIEW), &vk_image_view);
if (err) {
diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp
index 63ba6a6c96..f7632842ed 100644
--- a/drivers/windows/dir_access_windows.cpp
+++ b/drivers/windows/dir_access_windows.cpp
@@ -35,6 +35,7 @@
#include "core/config/project_settings.h"
#include "core/os/memory.h"
+#include "core/os/os.h"
#include "core/string/print_string.h"
#include <stdio.h>
@@ -69,9 +70,19 @@ struct DirAccessWindowsPrivate {
};
String DirAccessWindows::fix_path(const String &p_path) const {
- String r_path = DirAccess::fix_path(p_path);
- if (r_path.is_absolute_path() && !r_path.is_network_share_path() && r_path.length() > MAX_PATH) {
- r_path = "\\\\?\\" + r_path.replace("/", "\\");
+ String r_path = DirAccess::fix_path(p_path.trim_prefix(R"(\\?\)").replace("\\", "/"));
+ if (r_path.ends_with(":")) {
+ r_path += "/";
+ }
+ if (r_path.is_relative_path()) {
+ r_path = current_dir.trim_prefix(R"(\\?\)").replace("\\", "/").path_join(r_path);
+ } else if (r_path == ".") {
+ r_path = current_dir.trim_prefix(R"(\\?\)").replace("\\", "/");
+ }
+ r_path = r_path.simplify_path();
+ r_path = r_path.replace("/", "\\");
+ if (!r_path.is_network_share_path() && !r_path.begins_with(R"(\\?\)")) {
+ r_path = R"(\\?\)" + r_path;
}
return r_path;
}
@@ -140,28 +151,33 @@ String DirAccessWindows::get_drive(int p_drive) {
Error DirAccessWindows::change_dir(String p_dir) {
GLOBAL_LOCK_FUNCTION
- p_dir = fix_path(p_dir);
+ String dir = fix_path(p_dir);
- WCHAR real_current_dir_name[2048];
- GetCurrentDirectoryW(2048, real_current_dir_name);
- String prev_dir = String::utf16((const char16_t *)real_current_dir_name);
+ Char16String real_current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ real_current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw());
+ String prev_dir = String::utf16((const char16_t *)real_current_dir_name.get_data());
SetCurrentDirectoryW((LPCWSTR)(current_dir.utf16().get_data()));
- bool worked = (SetCurrentDirectoryW((LPCWSTR)(p_dir.utf16().get_data())) != 0);
+ bool worked = (SetCurrentDirectoryW((LPCWSTR)(dir.utf16().get_data())) != 0);
String base = _get_root_path();
if (!base.is_empty()) {
- GetCurrentDirectoryW(2048, real_current_dir_name);
- String new_dir = String::utf16((const char16_t *)real_current_dir_name).replace("\\", "/");
+ str_len = GetCurrentDirectoryW(0, nullptr);
+ real_current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw());
+ String new_dir = String::utf16((const char16_t *)real_current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace("\\", "/");
if (!new_dir.begins_with(base)) {
worked = false;
}
}
if (worked) {
- GetCurrentDirectoryW(2048, real_current_dir_name);
- current_dir = String::utf16((const char16_t *)real_current_dir_name);
- current_dir = current_dir.replace("\\", "/");
+ str_len = GetCurrentDirectoryW(0, nullptr);
+ real_current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw());
+ current_dir = String::utf16((const char16_t *)real_current_dir_name.get_data());
}
SetCurrentDirectoryW((LPCWSTR)(prev_dir.utf16().get_data()));
@@ -172,12 +188,6 @@ Error DirAccessWindows::change_dir(String p_dir) {
Error DirAccessWindows::make_dir(String p_dir) {
GLOBAL_LOCK_FUNCTION
- p_dir = fix_path(p_dir);
- if (p_dir.is_relative_path()) {
- p_dir = current_dir.path_join(p_dir);
- p_dir = fix_path(p_dir);
- }
-
if (FileAccessWindows::is_path_invalid(p_dir)) {
#ifdef DEBUG_ENABLED
WARN_PRINT("The path :" + p_dir + " is a reserved Windows system pipe, so it can't be used for creating directories.");
@@ -185,12 +195,12 @@ Error DirAccessWindows::make_dir(String p_dir) {
return ERR_INVALID_PARAMETER;
}
- p_dir = p_dir.simplify_path().replace("/", "\\");
+ String dir = fix_path(p_dir);
bool success;
int err;
- success = CreateDirectoryW((LPCWSTR)(p_dir.utf16().get_data()), nullptr);
+ success = CreateDirectoryW((LPCWSTR)(dir.utf16().get_data()), nullptr);
err = GetLastError();
if (success) {
@@ -205,9 +215,10 @@ Error DirAccessWindows::make_dir(String p_dir) {
}
String DirAccessWindows::get_current_dir(bool p_include_drive) const {
+ String cdir = current_dir.trim_prefix(R"(\\?\)").replace("\\", "/");
String base = _get_root_path();
if (!base.is_empty()) {
- String bd = current_dir.replace("\\", "/").replace_first(base, "");
+ String bd = cdir.replace_first(base, "");
if (bd.begins_with("/")) {
return _get_root_string() + bd.substr(1, bd.length());
} else {
@@ -216,30 +227,25 @@ String DirAccessWindows::get_current_dir(bool p_include_drive) const {
}
if (p_include_drive) {
- return current_dir;
+ return cdir;
} else {
if (_get_root_string().is_empty()) {
- int pos = current_dir.find(":");
+ int pos = cdir.find(":");
if (pos != -1) {
- return current_dir.substr(pos + 1);
+ return cdir.substr(pos + 1);
}
}
- return current_dir;
+ return cdir;
}
}
bool DirAccessWindows::file_exists(String p_file) {
GLOBAL_LOCK_FUNCTION
- if (!p_file.is_absolute_path()) {
- p_file = get_current_dir().path_join(p_file);
- }
-
- p_file = fix_path(p_file);
+ String file = fix_path(p_file);
DWORD fileAttr;
-
- fileAttr = GetFileAttributesW((LPCWSTR)(p_file.utf16().get_data()));
+ fileAttr = GetFileAttributesW((LPCWSTR)(file.utf16().get_data()));
if (INVALID_FILE_ATTRIBUTES == fileAttr) {
return false;
}
@@ -250,14 +256,10 @@ bool DirAccessWindows::file_exists(String p_file) {
bool DirAccessWindows::dir_exists(String p_dir) {
GLOBAL_LOCK_FUNCTION
- if (p_dir.is_relative_path()) {
- p_dir = get_current_dir().path_join(p_dir);
- }
-
- p_dir = fix_path(p_dir);
+ String dir = fix_path(p_dir);
DWORD fileAttr;
- fileAttr = GetFileAttributesW((LPCWSTR)(p_dir.utf16().get_data()));
+ fileAttr = GetFileAttributesW((LPCWSTR)(dir.utf16().get_data()));
if (INVALID_FILE_ATTRIBUTES == fileAttr) {
return false;
}
@@ -265,66 +267,63 @@ bool DirAccessWindows::dir_exists(String p_dir) {
}
Error DirAccessWindows::rename(String p_path, String p_new_path) {
- if (p_path.is_relative_path()) {
- p_path = get_current_dir().path_join(p_path);
- }
-
- p_path = fix_path(p_path);
-
- if (p_new_path.is_relative_path()) {
- p_new_path = get_current_dir().path_join(p_new_path);
- }
-
- p_new_path = fix_path(p_new_path);
+ String path = fix_path(p_path);
+ String new_path = fix_path(p_new_path);
// If we're only changing file name case we need to do a little juggling
- if (p_path.to_lower() == p_new_path.to_lower()) {
- if (dir_exists(p_path)) {
+ if (path.to_lower() == new_path.to_lower()) {
+ if (dir_exists(path)) {
// The path is a dir; just rename
- return ::_wrename((LPCWSTR)(p_path.utf16().get_data()), (LPCWSTR)(p_new_path.utf16().get_data())) == 0 ? OK : FAILED;
+ return MoveFileW((LPCWSTR)(path.utf16().get_data()), (LPCWSTR)(new_path.utf16().get_data())) != 0 ? OK : FAILED;
}
// The path is a file; juggle
- WCHAR tmpfile[MAX_PATH];
-
- if (!GetTempFileNameW((LPCWSTR)(fix_path(get_current_dir()).utf16().get_data()), nullptr, 0, tmpfile)) {
- return FAILED;
+ // Note: do not use GetTempFileNameW, it's not long path aware!
+ Char16String tmpfile_utf16;
+ uint64_t id = OS::get_singleton()->get_ticks_usec();
+ while (true) {
+ tmpfile_utf16 = (path + itos(id++) + ".tmp").utf16();
+ HANDLE handle = CreateFileW((LPCWSTR)tmpfile_utf16.get_data(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
+ if (handle != INVALID_HANDLE_VALUE) {
+ CloseHandle(handle);
+ break;
+ }
+ if (GetLastError() != ERROR_FILE_EXISTS && GetLastError() != ERROR_SHARING_VIOLATION) {
+ return FAILED;
+ }
}
- if (!::ReplaceFileW(tmpfile, (LPCWSTR)(p_path.utf16().get_data()), nullptr, 0, nullptr, nullptr)) {
- DeleteFileW(tmpfile);
+ if (!::ReplaceFileW((LPCWSTR)tmpfile_utf16.get_data(), (LPCWSTR)(path.utf16().get_data()), nullptr, 0, nullptr, nullptr)) {
+ DeleteFileW((LPCWSTR)tmpfile_utf16.get_data());
return FAILED;
}
- return ::_wrename(tmpfile, (LPCWSTR)(p_new_path.utf16().get_data())) == 0 ? OK : FAILED;
+ return MoveFileW((LPCWSTR)tmpfile_utf16.get_data(), (LPCWSTR)(new_path.utf16().get_data())) != 0 ? OK : FAILED;
} else {
- if (file_exists(p_new_path)) {
- if (remove(p_new_path) != OK) {
+ if (file_exists(new_path)) {
+ if (remove(new_path) != OK) {
return FAILED;
}
}
- return ::_wrename((LPCWSTR)(p_path.utf16().get_data()), (LPCWSTR)(p_new_path.utf16().get_data())) == 0 ? OK : FAILED;
+ return MoveFileW((LPCWSTR)(path.utf16().get_data()), (LPCWSTR)(new_path.utf16().get_data())) != 0 ? OK : FAILED;
}
}
Error DirAccessWindows::remove(String p_path) {
- if (p_path.is_relative_path()) {
- p_path = get_current_dir().path_join(p_path);
- }
-
- p_path = fix_path(p_path);
+ String path = fix_path(p_path);
+ const Char16String &path_utf16 = path.utf16();
DWORD fileAttr;
- fileAttr = GetFileAttributesW((LPCWSTR)(p_path.utf16().get_data()));
+ fileAttr = GetFileAttributesW((LPCWSTR)(path_utf16.get_data()));
if (INVALID_FILE_ATTRIBUTES == fileAttr) {
return FAILED;
}
if ((fileAttr & FILE_ATTRIBUTE_DIRECTORY)) {
- return ::_wrmdir((LPCWSTR)(p_path.utf16().get_data())) == 0 ? OK : FAILED;
+ return RemoveDirectoryW((LPCWSTR)(path_utf16.get_data())) != 0 ? OK : FAILED;
} else {
- return ::_wunlink((LPCWSTR)(p_path.utf16().get_data())) == 0 ? OK : FAILED;
+ return DeleteFileW((LPCWSTR)(path_utf16.get_data())) != 0 ? OK : FAILED;
}
}
@@ -339,16 +338,16 @@ uint64_t DirAccessWindows::get_space_left() {
}
String DirAccessWindows::get_filesystem_type() const {
- String path = fix_path(const_cast<DirAccessWindows *>(this)->get_current_dir());
-
- int unit_end = path.find(":");
- ERR_FAIL_COND_V(unit_end == -1, String());
- String unit = path.substr(0, unit_end + 1) + "\\";
+ String path = current_dir.trim_prefix(R"(\\?\)");
if (path.is_network_share_path()) {
return "Network Share";
}
+ int unit_end = path.find(":");
+ ERR_FAIL_COND_V(unit_end == -1, String());
+ String unit = path.substr(0, unit_end + 1) + "\\";
+
WCHAR szVolumeName[100];
WCHAR szFileSystemName[10];
DWORD dwSerialNumber = 0;
@@ -370,11 +369,7 @@ String DirAccessWindows::get_filesystem_type() const {
}
bool DirAccessWindows::is_case_sensitive(const String &p_path) const {
- String f = p_path;
- if (!f.is_absolute_path()) {
- f = get_current_dir().path_join(f);
- }
- f = fix_path(f);
+ String f = fix_path(p_path);
HANDLE h_file = ::CreateFileW((LPCWSTR)(f.utf16().get_data()), 0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
@@ -397,12 +392,7 @@ bool DirAccessWindows::is_case_sensitive(const String &p_path) const {
}
bool DirAccessWindows::is_link(String p_file) {
- String f = p_file;
-
- if (!f.is_absolute_path()) {
- f = get_current_dir().path_join(f);
- }
- f = fix_path(f);
+ String f = fix_path(p_file);
DWORD attr = GetFileAttributesW((LPCWSTR)(f.utf16().get_data()));
if (attr == INVALID_FILE_ATTRIBUTES) {
@@ -413,12 +403,7 @@ bool DirAccessWindows::is_link(String p_file) {
}
String DirAccessWindows::read_link(String p_file) {
- String f = p_file;
-
- if (!f.is_absolute_path()) {
- f = get_current_dir().path_join(f);
- }
- f = fix_path(f);
+ String f = fix_path(p_file);
HANDLE hfile = CreateFileW((LPCWSTR)(f.utf16().get_data()), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
if (hfile == INVALID_HANDLE_VALUE) {
@@ -434,22 +419,18 @@ String DirAccessWindows::read_link(String p_file) {
GetFinalPathNameByHandleW(hfile, (LPWSTR)cs.ptrw(), ret, VOLUME_NAME_DOS | FILE_NAME_NORMALIZED);
CloseHandle(hfile);
- return String::utf16((const char16_t *)cs.ptr(), ret).trim_prefix(R"(\\?\)");
+ return String::utf16((const char16_t *)cs.ptr(), ret).trim_prefix(R"(\\?\)").replace("\\", "/");
}
Error DirAccessWindows::create_link(String p_source, String p_target) {
- if (p_target.is_relative_path()) {
- p_target = get_current_dir().path_join(p_target);
- }
+ String source = fix_path(p_source);
+ String target = fix_path(p_target);
- p_source = fix_path(p_source);
- p_target = fix_path(p_target);
-
- DWORD file_attr = GetFileAttributesW((LPCWSTR)(p_source.utf16().get_data()));
+ DWORD file_attr = GetFileAttributesW((LPCWSTR)(source.utf16().get_data()));
bool is_dir = (file_attr & FILE_ATTRIBUTE_DIRECTORY);
DWORD flags = ((is_dir) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
- if (CreateSymbolicLinkW((LPCWSTR)p_target.utf16().get_data(), (LPCWSTR)p_source.utf16().get_data(), flags) != 0) {
+ if (CreateSymbolicLinkW((LPCWSTR)target.utf16().get_data(), (LPCWSTR)source.utf16().get_data(), flags) != 0) {
return OK;
} else {
return FAILED;
@@ -459,7 +440,12 @@ Error DirAccessWindows::create_link(String p_source, String p_target) {
DirAccessWindows::DirAccessWindows() {
p = memnew(DirAccessWindowsPrivate);
p->h = INVALID_HANDLE_VALUE;
- current_dir = ".";
+
+ Char16String real_current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ real_current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw());
+ current_dir = String::utf16((const char16_t *)real_current_dir_name.get_data());
DWORD mask = GetLogicalDrives();
diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp
index 9885d9d7ee..0243d863f8 100644
--- a/drivers/windows/file_access_windows.cpp
+++ b/drivers/windows/file_access_windows.cpp
@@ -73,8 +73,18 @@ bool FileAccessWindows::is_path_invalid(const String &p_path) {
String FileAccessWindows::fix_path(const String &p_path) const {
String r_path = FileAccess::fix_path(p_path);
- if (r_path.is_absolute_path() && !r_path.is_network_share_path() && r_path.length() > MAX_PATH) {
- r_path = "\\\\?\\" + r_path.replace("/", "\\");
+
+ if (r_path.is_relative_path()) {
+ Char16String current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());
+ r_path = String::utf16((const char16_t *)current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace("\\", "/").path_join(r_path);
+ }
+ r_path = r_path.simplify_path();
+ r_path = r_path.replace("/", "\\");
+ if (!r_path.is_network_share_path() && !r_path.begins_with(R"(\\?\)")) {
+ r_path = R"(\\?\)" + r_path;
}
return r_path;
}
@@ -108,9 +118,6 @@ Error FileAccessWindows::open_internal(const String &p_path, int p_mode_flags) {
return ERR_INVALID_PARAMETER;
}
- /* Pretty much every implementation that uses fopen as primary
- backend supports utf8 encoding. */
-
struct _stat st;
if (_wstat((LPCWSTR)(path.utf16().get_data()), &st) == 0) {
if (!S_ISREG(st.st_mode)) {
@@ -125,7 +132,7 @@ Error FileAccessWindows::open_internal(const String &p_path, int p_mode_flags) {
// platforms), we only check for relative paths, or paths in res:// or user://,
// other paths aren't likely to be portable anyway.
if (p_mode_flags == READ && (p_path.is_relative_path() || get_access_type() != ACCESS_FILESYSTEM)) {
- String base_path = path;
+ String base_path = p_path;
String working_path;
String proper_path;
@@ -144,23 +151,17 @@ Error FileAccessWindows::open_internal(const String &p_path, int p_mode_flags) {
}
proper_path = "user://";
}
+ working_path = fix_path(working_path);
WIN32_FIND_DATAW d;
- Vector<String> parts = base_path.split("/");
+ Vector<String> parts = base_path.simplify_path().split("/");
bool mismatch = false;
for (const String &part : parts) {
- working_path = working_path.path_join(part);
-
- // Skip if relative.
- if (part == "." || part == "..") {
- proper_path = proper_path.path_join(part);
- continue;
- }
+ working_path = working_path + "\\" + part;
HANDLE fnd = FindFirstFileW((LPCWSTR)(working_path.utf16().get_data()), &d);
-
if (fnd == INVALID_HANDLE_VALUE) {
mismatch = false;
break;
@@ -186,12 +187,22 @@ Error FileAccessWindows::open_internal(const String &p_path, int p_mode_flags) {
if (is_backup_save_enabled() && p_mode_flags == WRITE) {
save_path = path;
// Create a temporary file in the same directory as the target file.
- WCHAR tmpFileName[MAX_PATH];
- if (GetTempFileNameW((LPCWSTR)(path.get_base_dir().utf16().get_data()), (LPCWSTR)(path.get_file().utf16().get_data()), 0, tmpFileName) == 0) {
- last_error = ERR_FILE_CANT_OPEN;
- return last_error;
+ // Note: do not use GetTempFileNameW, it's not long path aware!
+ String tmpfile;
+ uint64_t id = OS::get_singleton()->get_ticks_usec();
+ while (true) {
+ tmpfile = path + itos(id++) + ".tmp";
+ HANDLE handle = CreateFileW((LPCWSTR)tmpfile.utf16().get_data(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
+ if (handle != INVALID_HANDLE_VALUE) {
+ CloseHandle(handle);
+ break;
+ }
+ if (GetLastError() != ERROR_FILE_EXISTS && GetLastError() != ERROR_SHARING_VIOLATION) {
+ last_error = ERR_FILE_CANT_WRITE;
+ return FAILED;
+ }
}
- path = tmpFileName;
+ path = tmpfile;
}
f = _wfsopen((LPCWSTR)(path.utf16().get_data()), mode_string, is_backup_save_enabled() ? _SH_SECURE : _SH_DENYNO);
@@ -235,7 +246,7 @@ void FileAccessWindows::_close() {
} else {
// Either the target exists and is locked (temporarily, hopefully)
// or it doesn't exist; let's assume the latter before re-trying.
- rename_error = _wrename((LPCWSTR)(path_utf16.get_data()), (LPCWSTR)(save_path_utf16.get_data())) != 0;
+ rename_error = MoveFileW((LPCWSTR)(path_utf16.get_data()), (LPCWSTR)(save_path_utf16.get_data())) == 0;
}
if (!rename_error) {
@@ -262,7 +273,7 @@ String FileAccessWindows::get_path() const {
}
String FileAccessWindows::get_path_absolute() const {
- return path;
+ return path.trim_prefix(R"(\\?\)").replace("\\", "/");
}
bool FileAccessWindows::is_open() const {
@@ -312,93 +323,9 @@ bool FileAccessWindows::eof_reached() const {
return last_error == ERR_FILE_EOF;
}
-uint8_t FileAccessWindows::get_8() const {
- ERR_FAIL_NULL_V(f, 0);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == WRITE) {
- fflush(f);
- }
- prev_op = READ;
- }
- uint8_t b;
- if (fread(&b, 1, 1, f) == 0) {
- check_errors();
- b = '\0';
- }
-
- return b;
-}
-
-uint16_t FileAccessWindows::get_16() const {
- ERR_FAIL_NULL_V(f, 0);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == WRITE) {
- fflush(f);
- }
- prev_op = READ;
- }
-
- uint16_t b = 0;
- if (fread(&b, 1, 2, f) != 2) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP16(b);
- }
-
- return b;
-}
-
-uint32_t FileAccessWindows::get_32() const {
- ERR_FAIL_NULL_V(f, 0);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == WRITE) {
- fflush(f);
- }
- prev_op = READ;
- }
-
- uint32_t b = 0;
- if (fread(&b, 1, 4, f) != 4) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP32(b);
- }
-
- return b;
-}
-
-uint64_t FileAccessWindows::get_64() const {
- ERR_FAIL_NULL_V(f, 0);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == WRITE) {
- fflush(f);
- }
- prev_op = READ;
- }
-
- uint64_t b = 0;
- if (fread(&b, 1, 8, f) != 8) {
- check_errors();
- }
-
- if (big_endian) {
- b = BSWAP64(b);
- }
-
- return b;
-}
-
uint64_t FileAccessWindows::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_NULL_V(f, -1);
+ ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
if (flags == READ_WRITE || flags == WRITE_READ) {
if (prev_op == WRITE) {
@@ -406,8 +333,10 @@ uint64_t FileAccessWindows::get_buffer(uint8_t *p_dst, uint64_t p_length) const
}
prev_op = READ;
}
+
uint64_t read = fread(p_dst, 1, p_length, f);
check_errors();
+
return read;
}
@@ -442,77 +371,6 @@ void FileAccessWindows::flush() {
}
}
-void FileAccessWindows::store_8(uint8_t p_dest) {
- ERR_FAIL_NULL(f);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == READ) {
- if (last_error != ERR_FILE_EOF) {
- fseek(f, 0, SEEK_CUR);
- }
- }
- prev_op = WRITE;
- }
- fwrite(&p_dest, 1, 1, f);
-}
-
-void FileAccessWindows::store_16(uint16_t p_dest) {
- ERR_FAIL_NULL(f);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == READ) {
- if (last_error != ERR_FILE_EOF) {
- fseek(f, 0, SEEK_CUR);
- }
- }
- prev_op = WRITE;
- }
-
- if (big_endian) {
- p_dest = BSWAP16(p_dest);
- }
-
- fwrite(&p_dest, 1, 2, f);
-}
-
-void FileAccessWindows::store_32(uint32_t p_dest) {
- ERR_FAIL_NULL(f);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == READ) {
- if (last_error != ERR_FILE_EOF) {
- fseek(f, 0, SEEK_CUR);
- }
- }
- prev_op = WRITE;
- }
-
- if (big_endian) {
- p_dest = BSWAP32(p_dest);
- }
-
- fwrite(&p_dest, 1, 4, f);
-}
-
-void FileAccessWindows::store_64(uint64_t p_dest) {
- ERR_FAIL_NULL(f);
-
- if (flags == READ_WRITE || flags == WRITE_READ) {
- if (prev_op == READ) {
- if (last_error != ERR_FILE_EOF) {
- fseek(f, 0, SEEK_CUR);
- }
- }
- prev_op = WRITE;
- }
-
- if (big_endian) {
- p_dest = BSWAP64(p_dest);
- }
-
- fwrite(&p_dest, 1, 8, f);
-}
-
void FileAccessWindows::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_NULL(f);
ERR_FAIL_COND(!p_src && p_length > 0);
@@ -525,6 +383,7 @@ void FileAccessWindows::store_buffer(const uint8_t *p_src, uint64_t p_length) {
}
prev_op = WRITE;
}
+
ERR_FAIL_COND(fwrite(p_src, 1, p_length, f) != (size_t)p_length);
}
@@ -549,7 +408,7 @@ uint64_t FileAccessWindows::_get_modified_time(const String &p_file) {
}
String file = fix_path(p_file);
- if (file.ends_with("/") && file != "/") {
+ if (file.ends_with("\\") && file != "\\") {
file = file.substr(0, file.length() - 1);
}
@@ -582,14 +441,15 @@ bool FileAccessWindows::_get_hidden_attribute(const String &p_file) {
Error FileAccessWindows::_set_hidden_attribute(const String &p_file, bool p_hidden) {
String file = fix_path(p_file);
+ const Char16String &file_utf16 = file.utf16();
- DWORD attrib = GetFileAttributesW((LPCWSTR)file.utf16().get_data());
+ DWORD attrib = GetFileAttributesW((LPCWSTR)file_utf16.get_data());
ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, FAILED, "Failed to get attributes for: " + p_file);
BOOL ok;
if (p_hidden) {
- ok = SetFileAttributesW((LPCWSTR)file.utf16().get_data(), attrib | FILE_ATTRIBUTE_HIDDEN);
+ ok = SetFileAttributesW((LPCWSTR)file_utf16.get_data(), attrib | FILE_ATTRIBUTE_HIDDEN);
} else {
- ok = SetFileAttributesW((LPCWSTR)file.utf16().get_data(), attrib & ~FILE_ATTRIBUTE_HIDDEN);
+ ok = SetFileAttributesW((LPCWSTR)file_utf16.get_data(), attrib & ~FILE_ATTRIBUTE_HIDDEN);
}
ERR_FAIL_COND_V_MSG(!ok, FAILED, "Failed to set attributes for: " + p_file);
@@ -606,14 +466,15 @@ bool FileAccessWindows::_get_read_only_attribute(const String &p_file) {
Error FileAccessWindows::_set_read_only_attribute(const String &p_file, bool p_ro) {
String file = fix_path(p_file);
+ const Char16String &file_utf16 = file.utf16();
- DWORD attrib = GetFileAttributesW((LPCWSTR)file.utf16().get_data());
+ DWORD attrib = GetFileAttributesW((LPCWSTR)file_utf16.get_data());
ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, FAILED, "Failed to get attributes for: " + p_file);
BOOL ok;
if (p_ro) {
- ok = SetFileAttributesW((LPCWSTR)file.utf16().get_data(), attrib | FILE_ATTRIBUTE_READONLY);
+ ok = SetFileAttributesW((LPCWSTR)file_utf16.get_data(), attrib | FILE_ATTRIBUTE_READONLY);
} else {
- ok = SetFileAttributesW((LPCWSTR)file.utf16().get_data(), attrib & ~FILE_ATTRIBUTE_READONLY);
+ ok = SetFileAttributesW((LPCWSTR)file_utf16.get_data(), attrib & ~FILE_ATTRIBUTE_READONLY);
}
ERR_FAIL_COND_V_MSG(!ok, FAILED, "Failed to set attributes for: " + p_file);
diff --git a/drivers/windows/file_access_windows.h b/drivers/windows/file_access_windows.h
index a25bbcfb3a..f458ff9c6c 100644
--- a/drivers/windows/file_access_windows.h
+++ b/drivers/windows/file_access_windows.h
@@ -69,20 +69,12 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
- virtual uint8_t get_8() const override; ///< get a byte
- virtual uint16_t get_16() const override;
- virtual uint32_t get_32() const override;
- virtual uint64_t get_64() const override;
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override;
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
- virtual void store_16(uint16_t p_dest) override;
- virtual void store_32(uint32_t p_dest) override;
- virtual void store_64(uint64_t p_dest) override;
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_name) override; ///< return true if a file exists
diff --git a/drivers/windows/file_access_windows_pipe.cpp b/drivers/windows/file_access_windows_pipe.cpp
index 7902c8e1d8..0c953b14aa 100644
--- a/drivers/windows/file_access_windows_pipe.cpp
+++ b/drivers/windows/file_access_windows_pipe.cpp
@@ -96,22 +96,9 @@ String FileAccessWindowsPipe::get_path_absolute() const {
return path_src;
}
-uint8_t FileAccessWindowsPipe::get_8() const {
- ERR_FAIL_COND_V_MSG(fd[0] == 0, 0, "Pipe must be opened before use.");
-
- uint8_t b;
- if (!ReadFile(fd[0], &b, 1, nullptr, nullptr)) {
- last_error = ERR_FILE_CANT_READ;
- b = '\0';
- } else {
- last_error = OK;
- }
- return b;
-}
-
uint64_t FileAccessWindowsPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
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;
if (!ReadFile(fd[0], p_dst, p_length, &read, nullptr) || read != p_length) {
@@ -126,15 +113,6 @@ Error FileAccessWindowsPipe::get_error() const {
return last_error;
}
-void FileAccessWindowsPipe::store_8(uint8_t p_src) {
- ERR_FAIL_COND_MSG(fd[1] == 0, "Pipe must be opened before use.");
- if (!WriteFile(fd[1], &p_src, 1, nullptr, nullptr)) {
- last_error = ERR_FILE_CANT_WRITE;
- } else {
- last_error = OK;
- }
-}
-
void FileAccessWindowsPipe::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND_MSG(fd[1] == 0, "Pipe must be opened before use.");
ERR_FAIL_COND(!p_src && p_length > 0);
diff --git a/drivers/windows/file_access_windows_pipe.h b/drivers/windows/file_access_windows_pipe.h
index b885ef78e6..4e9bd036ae 100644
--- a/drivers/windows/file_access_windows_pipe.h
+++ b/drivers/windows/file_access_windows_pipe.h
@@ -64,14 +64,12 @@ public:
virtual bool eof_reached() const override { return false; }
- virtual uint8_t get_8() const override; ///< get a byte
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
virtual void flush() override {}
- virtual void store_8(uint8_t p_src) override; ///< store a byte
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
virtual bool file_exists(const String &p_name) override { return false; }
diff --git a/editor/action_map_editor.cpp b/editor/action_map_editor.cpp
index 6b237366fd..16423fb111 100644
--- a/editor/action_map_editor.cpp
+++ b/editor/action_map_editor.cpp
@@ -584,7 +584,7 @@ ActionMapEditor::ActionMapEditor() {
show_builtin_actions_checkbutton = memnew(CheckButton);
show_builtin_actions_checkbutton->set_text(TTR("Show Built-in Actions"));
- show_builtin_actions_checkbutton->connect("toggled", callable_mp(this, &ActionMapEditor::set_show_builtin_actions));
+ show_builtin_actions_checkbutton->connect(SceneStringName(toggled), callable_mp(this, &ActionMapEditor::set_show_builtin_actions));
add_hbox->add_child(show_builtin_actions_checkbutton);
show_builtin_actions = EditorSettings::get_singleton()->get_project_metadata("project_settings", "show_builtin_actions", false);
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp
index 5196857240..a2fba2c41e 100644
--- a/editor/animation_bezier_editor.cpp
+++ b/editor/animation_bezier_editor.cpp
@@ -1660,7 +1660,7 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
switch (p_index) {
case MENU_KEY_INSERT: {
if (animation->get_track_count() > 0) {
- if (editor->snap->is_pressed() && editor->step->get_value() != 0) {
+ if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) {
time = editor->snap_time(time);
}
while (animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX) != -1) {
@@ -1736,7 +1736,7 @@ void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs, bool p_ofs_
real_t insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
if (p_ofs_valid) {
- if (editor->snap->is_pressed() && editor->step->get_value() != 0) {
+ if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) {
insert_pos = editor->snap_time(insert_pos);
}
}
@@ -1859,7 +1859,7 @@ void AnimationBezierTrackEdit::paste_keys(real_t p_ofs, bool p_ofs_valid) {
float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
if (p_ofs_valid) {
- if (editor->snap->is_pressed() && editor->step->get_value() != 0) {
+ if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) {
insert_pos = editor->snap_time(insert_pos);
}
}
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index f4403780c2..95ba301282 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -41,6 +41,7 @@
#include "editor/gui/editor_spin_slider.h"
#include "editor/gui/scene_tree_editor.h"
#include "editor/inspector_dock.h"
+#include "editor/multi_node_edit.h"
#include "editor/plugins/animation_player_editor_plugin.h"
#include "editor/themes/editor_scale.h"
#include "scene/3d/mesh_instance_3d.h"
@@ -120,12 +121,18 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu
float val = p_value;
float prev_val = animation->track_get_key_transition(track, key);
setting = true;
+
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Animation Change Transition"), UndoRedo::MERGE_ENDS);
undo_redo->add_do_method(animation.ptr(), "track_set_key_transition", track, key, val);
undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", track, key, prev_val);
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;
@@ -177,12 +184,18 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu
}
setting = true;
+
undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS);
Variant prev = animation->track_get_key_value(track, key);
undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, value);
undo_redo->add_undo_method(animation.ptr(), "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;
@@ -1540,14 +1553,18 @@ void AnimationTimelineEdit::_notification(int p_what) {
max_digit_width = MAX(digit_width, max_digit_width);
}
const int max_sc = int(Math::ceil(zoomw / scale));
- const int max_sc_width = String::num(max_sc).length() * max_digit_width;
+ const int max_sc_width = String::num(max_sc).length() * Math::ceil(max_digit_width);
+
+ const int min_margin = MAX(text_secondary_margin, text_primary_margin);
while (!step_found) {
int min = max_sc_width;
if (decimals > 0) {
- min += period_width + max_digit_width * decimals;
+ min += Math::ceil(period_width + max_digit_width * decimals);
}
+ min += (min_margin * 2);
+
static const int _multp[3] = { 1, 2, 5 };
for (int i = 0; i < 3; i++) {
step = (_multp[i] * dec);
@@ -1603,10 +1620,11 @@ void AnimationTimelineEdit::_notification(int p_what) {
int sc = int(Math::floor(pos * SC_ADJ));
int prev_sc = int(Math::floor(prev * SC_ADJ));
- bool sub = (sc % SC_ADJ);
if ((sc / step) != (prev_sc / step) || (prev_sc < 0 && sc >= 0)) {
int scd = sc < 0 ? prev_sc : sc;
+ bool sub = (((scd - (scd % step)) % (dec * 10)) != 0);
+
int line_margin = sub ? v_line_secondary_margin : v_line_primary_margin;
int line_width = sub ? v_line_secondary_width : v_line_primary_width;
Color line_color = sub ? v_line_secondary_color : v_line_primary_color;
@@ -3281,6 +3299,11 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
undo_redo->create_action(TTR("Change Animation Update Mode"));
undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", track, update_mode);
undo_redo->add_undo_method(animation.ptr(), "value_track_set_update_mode", track, animation->value_track_get_update_mode(track));
+ 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();
@@ -3295,6 +3318,11 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
undo_redo->create_action(TTR("Change Animation Interpolation Mode"));
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", track, interp_mode);
undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", track, animation->track_get_interpolation_type(track));
+ 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();
} break;
@@ -3305,6 +3333,11 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
undo_redo->create_action(TTR("Change Animation Loop Mode"));
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", track, loop_wrap);
undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_loop_wrap", track, animation->track_get_interpolation_loop_wrap(track));
+ 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();
@@ -3582,7 +3615,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re
_update_step_spinbox();
step->set_block_signals(false);
step->set_read_only(false);
- snap->set_disabled(false);
+ snap_keys->set_disabled(false);
+ snap_timeline->set_disabled(false);
snap_mode->set_disabled(false);
auto_fit->set_disabled(false);
auto_fit_bezier->set_disabled(false);
@@ -3603,7 +3637,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re
step->set_value(0);
step->set_block_signals(false);
step->set_read_only(true);
- snap->set_disabled(true);
+ snap_keys->set_disabled(true);
+ snap_timeline->set_disabled(true);
snap_mode->set_disabled(true);
bezier_edit_icon->set_disabled(true);
auto_fit->set_disabled(true);
@@ -3661,7 +3696,7 @@ void AnimationTrackEditor::update_keying() {
EditorSelectionHistory *editor_history = EditorNode::get_singleton()->get_editor_selection_history();
if (is_visible_in_tree() && animation.is_valid() && editor_history->get_path_size() > 0) {
Object *obj = ObjectDB::get_instance(editor_history->get_path_object(0));
- keying_enabled = Object::cast_to<Node>(obj) != nullptr;
+ keying_enabled = Object::cast_to<Node>(obj) != nullptr || Object::cast_to<MultiNodeEdit>(obj) != nullptr;
}
if (keying_enabled == keying) {
@@ -4078,19 +4113,20 @@ void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant
_query_insert(id);
}
-void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists) {
+void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_property, bool p_only_if_exists, bool p_advance) {
ERR_FAIL_NULL(root);
// Let's build a node path.
- Node *node = p_node;
- String path = root->get_path_to(node, true);
+ String path = root->get_path_to(p_node, true);
+
+ Variant value = p_node->get(p_property);
- if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") {
- if (node == AnimationPlayerEditor::get_singleton()->get_player()) {
+ if (Object::cast_to<AnimationPlayer>(p_node) && p_property == "current_animation") {
+ if (p_node == AnimationPlayerEditor::get_singleton()->get_player()) {
EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
return;
}
- _insert_animation_key(path, p_value);
+ _insert_animation_key(path, value);
return;
}
@@ -4118,26 +4154,26 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p
InsertData id;
id.path = np;
id.track_idx = i;
- id.value = p_value;
+ id.value = value;
id.type = Animation::TYPE_VALUE;
// TRANSLATORS: This describes the target of new animation track, will be inserted into another string.
id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = false;
+ id.advance = p_advance;
// Dialog insert.
_query_insert(id);
inserted = true;
} else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) {
- Variant value;
+ Variant actual_value;
String track_path = animation->track_get_path(i);
if (track_path == np) {
- value = p_value; // All good.
+ actual_value = value; // All good.
} else {
int sep = track_path.rfind(":");
if (sep != -1) {
String base_path = track_path.substr(0, sep);
if (base_path == np) {
String value_name = track_path.substr(sep + 1);
- value = p_value.get(value_name);
+ actual_value = value.get(value_name);
} else {
continue;
}
@@ -4149,10 +4185,10 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p
InsertData id;
id.path = animation->track_get_path(i);
id.track_idx = i;
- id.value = value;
+ id.value = actual_value;
id.type = Animation::TYPE_BEZIER;
id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = false;
+ id.advance = p_advance;
// Dialog insert.
_query_insert(id);
inserted = true;
@@ -4165,105 +4201,41 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p
InsertData id;
id.path = np;
id.track_idx = -1;
- id.value = p_value;
+ id.value = value;
id.type = Animation::TYPE_VALUE;
id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = false;
+ id.advance = p_advance;
// Dialog insert.
_query_insert(id);
}
-void AnimationTrackEditor::insert_value_key(const String &p_property, const Variant &p_value, bool p_advance) {
+void AnimationTrackEditor::insert_value_key(const String &p_property, bool p_advance) {
EditorSelectionHistory *history = EditorNode::get_singleton()->get_editor_selection_history();
ERR_FAIL_NULL(root);
ERR_FAIL_COND(history->get_path_size() == 0);
Object *obj = ObjectDB::get_instance(history->get_path_object(0));
- ERR_FAIL_NULL(Object::cast_to<Node>(obj));
-
- // Let's build a node path.
- Node *node = Object::cast_to<Node>(obj);
- String path = root->get_path_to(node, true);
-
- if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") {
- if (node == AnimationPlayerEditor::get_singleton()->get_player()) {
- EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
- return;
- }
- _insert_animation_key(path, p_value);
- return;
- }
-
- for (int i = 1; i < history->get_path_size(); i++) {
- String prop = history->get_path_property(i);
- ERR_FAIL_COND(prop.is_empty());
- path += ":" + prop;
- }
-
- path += ":" + p_property;
-
- NodePath np = path;
-
- // Locate track.
-
- bool inserted = false;
- make_insert_queue();
- for (int i = 0; i < animation->get_track_count(); i++) {
- if (animation->track_get_type(i) == Animation::TYPE_VALUE) {
- if (animation->track_get_path(i) != np) {
- continue;
- }
+ Ref<MultiNodeEdit> multi_node_edit(obj);
+ if (multi_node_edit.is_valid()) {
+ Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
+ ERR_FAIL_NULL(edited_scene);
- InsertData id;
- id.path = np;
- id.track_idx = i;
- id.value = p_value;
- id.type = Animation::TYPE_VALUE;
- id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = p_advance;
- // Dialog insert.
- _query_insert(id);
- inserted = true;
- } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) {
- Variant value;
- if (animation->track_get_path(i) == np) {
- value = p_value; // All good.
- } else {
- String tpath = animation->track_get_path(i);
- int index = tpath.rfind(":");
- if (NodePath(tpath.substr(0, index + 1)) == np) {
- String subindex = tpath.substr(index + 1, tpath.length() - index);
- value = p_value.get(subindex);
- } else {
- continue;
- }
- }
+ make_insert_queue();
- InsertData id;
- id.path = animation->track_get_path(i);
- id.track_idx = i;
- id.value = value;
- id.type = Animation::TYPE_BEZIER;
- id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = p_advance;
- // Dialog insert.
- _query_insert(id);
- inserted = true;
+ for (int i = 0; i < multi_node_edit->get_node_count(); ++i) {
+ Node *node = edited_scene->get_node(multi_node_edit->get_node(i));
+ insert_node_value_key(node, p_property, false, p_advance);
}
- }
- commit_insert_queue();
- if (!inserted) {
- InsertData id;
- id.path = np;
- id.track_idx = -1;
- id.value = p_value;
- id.type = Animation::TYPE_VALUE;
- id.query = vformat(TTR("property '%s'"), p_property);
- id.advance = p_advance;
- // Dialog insert.
- _query_insert(id);
+ commit_insert_queue();
+ } else {
+ Node *node = Object::cast_to<Node>(obj);
+ ERR_FAIL_NULL(node);
+
+ make_insert_queue();
+ insert_node_value_key(node, p_property, false, p_advance);
+ commit_insert_queue();
}
}
@@ -4585,8 +4557,16 @@ bool AnimationTrackEditor::is_key_clipboard_active() const {
return key_clipboard.keys.size();
}
-bool AnimationTrackEditor::is_snap_enabled() const {
- return snap->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
+bool AnimationTrackEditor::is_snap_timeline_enabled() const {
+ return snap_timeline->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
+}
+
+bool AnimationTrackEditor::is_snap_keys_enabled() const {
+ return snap_keys->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
+}
+
+bool AnimationTrackEditor::is_bezier_editor_active() const {
+ return bezier_edit->is_visible();
}
bool AnimationTrackEditor::can_add_reset_key() const {
@@ -4922,7 +4902,8 @@ void AnimationTrackEditor::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED: {
zoom_icon->set_texture(get_editor_theme_icon(SNAME("Zoom")));
bezier_edit_icon->set_icon(get_editor_theme_icon(SNAME("EditBezier")));
- snap->set_icon(get_editor_theme_icon(SNAME("Snap")));
+ snap_timeline->set_icon(get_editor_theme_icon(SNAME("SnapTimeline")));
+ snap_keys->set_icon(get_editor_theme_icon(SNAME("SnapKeys")));
view_group->set_icon(get_editor_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup")));
selected_filter->set_icon(get_editor_theme_icon(SNAME("AnimationFilter")));
imported_anim_warning->set_icon(get_editor_theme_icon(SNAME("NodeWarning")));
@@ -5243,7 +5224,7 @@ int AnimationTrackEditor::_get_track_selected() {
void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
ERR_FAIL_INDEX(p_track, animation->get_track_count());
- if (snap->is_pressed() && step->get_value() != 0) {
+ if (snap_keys->is_pressed() && step->get_value() != 0) {
p_ofs = snap_time(p_ofs);
}
while (animation->track_find_key(p_track, p_ofs, Animation::FIND_MODE_APPROX) != -1) { // Make sure insertion point is valid.
@@ -5650,6 +5631,14 @@ void AnimationTrackEditor::_move_selection_commit() {
moving_selection = false;
undo_redo->add_do_method(this, "_redraw_tracks");
undo_redo->add_undo_method(this, "_redraw_tracks");
+
+ // Update key frame.
+ 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();
}
@@ -5858,7 +5847,7 @@ void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, bool p_ofs_valid, i
float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
if (p_ofs_valid) {
- if (snap->is_pressed() && step->get_value() != 0) {
+ if (snap_keys->is_pressed() && step->get_value() != 0) {
insert_pos = snap_time(insert_pos);
}
}
@@ -6006,7 +5995,7 @@ void AnimationTrackEditor::_anim_paste_keys(float p_ofs, bool p_ofs_valid, int p
float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
if (p_ofs_valid) {
- if (snap->is_pressed() && step->get_value() != 0) {
+ if (snap_keys->is_pressed() && step->get_value() != 0) {
insert_pos = snap_time(insert_pos);
}
}
@@ -6887,8 +6876,8 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
_redraw_tracks();
_update_key_edit();
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->clear_history(true, undo_redo->get_history_id_for_object(animation.ptr()));
- undo_redo->clear_history(true, undo_redo->get_history_id_for_object(this));
+ undo_redo->clear_history(undo_redo->get_history_id_for_object(animation.ptr()));
+ undo_redo->clear_history(undo_redo->get_history_id_for_object(this));
} break;
case EDIT_CLEAN_UP_ANIMATION: {
@@ -7030,8 +7019,8 @@ void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) {
}
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->clear_history(true, undo_redo->get_history_id_for_object(animation.ptr()));
- undo_redo->clear_history(true, undo_redo->get_history_id_for_object(this));
+ undo_redo->clear_history(undo_redo->get_history_id_for_object(animation.ptr()));
+ undo_redo->clear_history(undo_redo->get_history_id_for_object(this));
_update_tracks();
}
@@ -7079,12 +7068,15 @@ void AnimationTrackEditor::_update_snap_unit() {
if (timeline->is_using_fps()) {
snap_unit = 1.0 / step->get_value();
} else {
- snap_unit = 1.0 / Math::round(1.0 / step->get_value()); // Follow the snap behavior of the timeline editor.
+ double integer;
+ double fraction = Math::modf(step->get_value(), &integer);
+ fraction = 1.0 / Math::round(1.0 / fraction);
+ snap_unit = integer + fraction;
}
}
float AnimationTrackEditor::snap_time(float p_value, bool p_relative) {
- if (is_snap_enabled()) {
+ if (is_snap_keys_enabled()) {
if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
// Use more precise snapping when holding Shift.
snap_unit *= 0.25;
@@ -7338,13 +7330,21 @@ AnimationTrackEditor::AnimationTrackEditor() {
bottom_hb->add_child(view_group);
bottom_hb->add_child(memnew(VSeparator));
- snap = memnew(Button);
- snap->set_flat(true);
- snap->set_text(TTR("Snap:") + " ");
- bottom_hb->add_child(snap);
- snap->set_disabled(true);
- snap->set_toggle_mode(true);
- snap->set_pressed(true);
+ snap_timeline = memnew(Button);
+ snap_timeline->set_flat(true);
+ bottom_hb->add_child(snap_timeline);
+ snap_timeline->set_disabled(true);
+ snap_timeline->set_toggle_mode(true);
+ snap_timeline->set_pressed(false);
+ snap_timeline->set_tooltip_text(TTR("Apply snapping to timeline cursor."));
+
+ snap_keys = memnew(Button);
+ snap_keys->set_flat(true);
+ bottom_hb->add_child(snap_keys);
+ snap_keys->set_disabled(true);
+ snap_keys->set_toggle_mode(true);
+ snap_keys->set_pressed(true);
+ snap_keys->set_tooltip_text(TTR("Apply snapping to selected key(s)."));
step = memnew(EditorSpinSlider);
step->set_min(0);
@@ -7713,6 +7713,8 @@ void AnimationTrackKeyEditEditor::_time_edit_exited() {
undo_redo->add_do_method(ate, "_select_at_anim", animation, track, new_time);
undo_redo->add_undo_method(ate, "_select_at_anim", animation, track, key_data_cache.time);
}
+ undo_redo->add_do_method(ape, "_animation_update_key_frame");
+ undo_redo->add_undo_method(ape, "_animation_update_key_frame");
}
undo_redo->commit_action();
diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h
index 59ee6535ac..8a263d7d20 100644
--- a/editor/animation_track_editor.h
+++ b/editor/animation_track_editor.h
@@ -408,7 +408,8 @@ class AnimationTrackEditor : public VBoxContainer {
HSlider *zoom = nullptr;
EditorSpinSlider *step = nullptr;
TextureRect *zoom_icon = nullptr;
- Button *snap = nullptr;
+ Button *snap_keys = nullptr;
+ Button *snap_timeline = nullptr;
Button *bezier_edit_icon = nullptr;
OptionButton *snap_mode = nullptr;
Button *auto_fit = nullptr;
@@ -713,8 +714,8 @@ public:
void cleanup();
void set_anim_pos(float p_pos);
- void insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists = false);
- void insert_value_key(const String &p_property, const Variant &p_value, bool p_advance);
+ void insert_node_value_key(Node *p_node, const String &p_property, bool p_only_if_exists = false, bool p_advance = false);
+ void insert_value_key(const String &p_property, bool p_advance);
void insert_transform_key(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type, const Variant &p_value);
bool has_track(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type);
void make_insert_queue();
@@ -728,7 +729,9 @@ public:
bool is_selection_active() const;
bool is_key_clipboard_active() const;
bool is_moving_selection() const;
- bool is_snap_enabled() const;
+ bool is_snap_timeline_enabled() const;
+ bool is_snap_keys_enabled() const;
+ bool is_bezier_editor_active() const;
bool can_add_reset_key() const;
float get_moving_selection_offset() const;
float snap_time(float p_value, bool p_relative = false);
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 8664c167b5..dd8aa523c4 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -755,13 +755,13 @@ FindReplaceBar::FindReplaceBar() {
hbc_option_search->add_child(case_sensitive);
case_sensitive->set_text(TTR("Match Case"));
case_sensitive->set_focus_mode(FOCUS_NONE);
- case_sensitive->connect("toggled", callable_mp(this, &FindReplaceBar::_search_options_changed));
+ case_sensitive->connect(SceneStringName(toggled), callable_mp(this, &FindReplaceBar::_search_options_changed));
whole_words = memnew(CheckBox);
hbc_option_search->add_child(whole_words);
whole_words->set_text(TTR("Whole Words"));
whole_words->set_focus_mode(FOCUS_NONE);
- whole_words->connect("toggled", callable_mp(this, &FindReplaceBar::_search_options_changed));
+ whole_words->connect(SceneStringName(toggled), callable_mp(this, &FindReplaceBar::_search_options_changed));
// Replace toolbar
replace_text = memnew(LineEdit);
@@ -786,7 +786,7 @@ FindReplaceBar::FindReplaceBar() {
hbc_option_replace->add_child(selection_only);
selection_only->set_text(TTR("Selection Only"));
selection_only->set_focus_mode(FOCUS_NONE);
- selection_only->connect("toggled", callable_mp(this, &FindReplaceBar::_search_options_changed));
+ selection_only->connect(SceneStringName(toggled), callable_mp(this, &FindReplaceBar::_search_options_changed));
hide_button = memnew(TextureButton);
add_child(hide_button);
@@ -1299,23 +1299,29 @@ void CodeTextEditor::toggle_inline_comment(const String &delimiter) {
text_editor->end_complex_operation();
}
-void CodeTextEditor::goto_line(int p_line) {
+void CodeTextEditor::goto_line(int p_line, int p_column) {
text_editor->remove_secondary_carets();
text_editor->deselect();
- text_editor->unfold_line(p_line);
- callable_mp((TextEdit *)text_editor, &TextEdit::set_caret_line).call_deferred(p_line, true, true, 0, 0);
+ text_editor->unfold_line(CLAMP(p_line, 0, text_editor->get_line_count() - 1));
+ text_editor->set_caret_line(p_line, false);
+ text_editor->set_caret_column(p_column, false);
+ // Defer in case the CodeEdit was just created and needs to be resized.
+ callable_mp((TextEdit *)text_editor, &TextEdit::adjust_viewport_to_caret).call_deferred(0);
}
void CodeTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) {
text_editor->remove_secondary_carets();
- text_editor->unfold_line(p_line);
- callable_mp((TextEdit *)text_editor, &TextEdit::set_caret_line).call_deferred(p_line, true, true, 0, 0);
- callable_mp((TextEdit *)text_editor, &TextEdit::set_caret_column).call_deferred(p_begin, true, 0);
+ text_editor->unfold_line(CLAMP(p_line, 0, text_editor->get_line_count() - 1));
text_editor->select(p_line, p_begin, p_line, p_end);
+ callable_mp((TextEdit *)text_editor, &TextEdit::adjust_viewport_to_caret).call_deferred(0);
}
-void CodeTextEditor::goto_line_centered(int p_line) {
- goto_line(p_line);
+void CodeTextEditor::goto_line_centered(int p_line, int p_column) {
+ text_editor->remove_secondary_carets();
+ text_editor->deselect();
+ text_editor->unfold_line(CLAMP(p_line, 0, text_editor->get_line_count() - 1));
+ text_editor->set_caret_line(p_line, false);
+ text_editor->set_caret_column(p_column, false);
callable_mp((TextEdit *)text_editor, &TextEdit::center_viewport_to_caret).call_deferred(0);
}
@@ -1443,13 +1449,7 @@ void CodeTextEditor::goto_error() {
corrected_column -= tab_count * (indent_size - 1);
}
- if (text_editor->get_line_count() != error_line) {
- text_editor->unfold_line(error_line);
- }
- text_editor->remove_secondary_carets();
- text_editor->set_caret_line(error_line);
- text_editor->set_caret_column(corrected_column);
- text_editor->center_viewport_to_caret();
+ goto_line_centered(error_line, corrected_column);
}
}
@@ -1548,7 +1548,8 @@ void CodeTextEditor::_set_show_warnings_panel(bool p_show) {
}
void CodeTextEditor::_toggle_scripts_pressed() {
- ScriptEditor::get_singleton()->toggle_scripts_panel();
+ ERR_FAIL_NULL(toggle_scripts_list);
+ toggle_scripts_list->set_visible(!toggle_scripts_list->is_visible());
update_toggle_scripts_button();
}
@@ -1723,16 +1724,18 @@ void CodeTextEditor::set_code_complete_func(CodeTextEditorCodeCompleteFunc p_cod
code_complete_ud = p_ud;
}
+void CodeTextEditor::set_toggle_list_control(Control *p_control) {
+ toggle_scripts_list = p_control;
+}
+
void CodeTextEditor::show_toggle_scripts_button() {
toggle_scripts_button->show();
}
void CodeTextEditor::update_toggle_scripts_button() {
- if (is_layout_rtl()) {
- toggle_scripts_button->set_icon(get_editor_theme_icon(ScriptEditor::get_singleton()->is_scripts_panel_toggled() ? SNAME("Forward") : SNAME("Back")));
- } else {
- toggle_scripts_button->set_icon(get_editor_theme_icon(ScriptEditor::get_singleton()->is_scripts_panel_toggled() ? SNAME("Back") : SNAME("Forward")));
- }
+ ERR_FAIL_NULL(toggle_scripts_list);
+ bool forward = toggle_scripts_list->is_visible() == is_layout_rtl();
+ toggle_scripts_button->set_icon(get_editor_theme_icon(forward ? SNAME("Forward") : SNAME("Back")));
toggle_scripts_button->set_tooltip_text(vformat("%s (%s)", TTR("Toggle Scripts Panel"), ED_GET_SHORTCUT("script_editor/toggle_scripts_panel")->get_as_text()));
}
diff --git a/editor/code_editor.h b/editor/code_editor.h
index 28f6944b66..e56405a4b2 100644
--- a/editor/code_editor.h
+++ b/editor/code_editor.h
@@ -161,6 +161,7 @@ class CodeTextEditor : public VBoxContainer {
HBoxContainer *status_bar = nullptr;
Button *toggle_scripts_button = nullptr;
+ Control *toggle_scripts_list = nullptr;
Button *error_button = nullptr;
Button *warning_button = nullptr;
@@ -246,9 +247,9 @@ public:
/// by adding or removing comment delimiter
void toggle_inline_comment(const String &delimiter);
- void goto_line(int p_line);
+ void goto_line(int p_line, int p_column = 0);
void goto_line_selection(int p_line, int p_begin, int p_end);
- void goto_line_centered(int p_line);
+ void goto_line_centered(int p_line, int p_column = 0);
void set_executing_line(int p_line);
void clear_executing_line();
@@ -285,6 +286,7 @@ public:
void validate_script();
+ void set_toggle_list_control(Control *p_control);
void show_toggle_scripts_button();
void update_toggle_scripts_button();
diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp
index d3bd18c0e8..b4265f9fc0 100644
--- a/editor/debugger/editor_debugger_node.cpp
+++ b/editor/debugger/editor_debugger_node.cpp
@@ -213,8 +213,8 @@ void EditorDebuggerNode::_bind_methods() {
}
void EditorDebuggerNode::register_undo_redo(UndoRedo *p_undo_redo) {
- p_undo_redo->set_method_notify_callback(_method_changeds, this);
- p_undo_redo->set_property_notify_callback(_property_changeds, this);
+ p_undo_redo->set_method_notify_callback(_methods_changed, this);
+ p_undo_redo->set_property_notify_callback(_properties_changed, this);
}
EditorDebuggerRemoteObject *EditorDebuggerNode::get_inspected_remote_object() {
@@ -303,7 +303,7 @@ void EditorDebuggerNode::stop(bool p_force) {
});
_break_state_changed();
breakpoints.clear();
- EditorUndoRedoManager::get_singleton()->clear_history(false, EditorUndoRedoManager::REMOTE_HISTORY);
+ EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::REMOTE_HISTORY, false);
set_process(false);
}
@@ -720,7 +720,7 @@ void EditorDebuggerNode::_breakpoints_cleared_in_tree(int p_debugger) {
}
// Remote inspector/edit.
-void EditorDebuggerNode::_method_changeds(void *p_ud, Object *p_base, const StringName &p_name, const Variant **p_args, int p_argcount) {
+void EditorDebuggerNode::_methods_changed(void *p_ud, Object *p_base, const StringName &p_name, const Variant **p_args, int p_argcount) {
if (!singleton) {
return;
}
@@ -729,7 +729,7 @@ void EditorDebuggerNode::_method_changeds(void *p_ud, Object *p_base, const Stri
});
}
-void EditorDebuggerNode::_property_changeds(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value) {
+void EditorDebuggerNode::_properties_changed(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value) {
if (!singleton) {
return;
}
diff --git a/editor/debugger/editor_debugger_node.h b/editor/debugger/editor_debugger_node.h
index aef1d84758..12e097f652 100644
--- a/editor/debugger/editor_debugger_node.h
+++ b/editor/debugger/editor_debugger_node.h
@@ -193,8 +193,8 @@ public:
// Remote inspector/edit.
void request_remote_tree();
- static void _method_changeds(void *p_ud, Object *p_base, const StringName &p_name, const Variant **p_args, int p_argcount);
- static void _property_changeds(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value);
+ static void _methods_changed(void *p_ud, Object *p_base, const StringName &p_name, const Variant **p_args, int p_argcount);
+ static void _properties_changed(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value);
// LiveDebug
void set_live_debugging(bool p_enabled);
diff --git a/editor/editor_asset_installer.cpp b/editor/editor_asset_installer.cpp
index b415c72c15..1e44a9bdc9 100644
--- a/editor/editor_asset_installer.cpp
+++ b/editor/editor_asset_installer.cpp
@@ -685,7 +685,7 @@ EditorAssetInstaller::EditorAssetInstaller() {
show_source_files_button->set_toggle_mode(true);
show_source_files_button->set_tooltip_text(TTR("Open the list of the asset contents and select which files to install."));
remapping_tools->add_child(show_source_files_button);
- show_source_files_button->connect("toggled", callable_mp(this, &EditorAssetInstaller::_toggle_source_tree).bind(false));
+ show_source_files_button->connect(SceneStringName(toggled), callable_mp(this, &EditorAssetInstaller::_toggle_source_tree).bind(false));
Button *target_dir_button = memnew(Button);
target_dir_button->set_text(TTR("Change Install Folder"));
@@ -698,7 +698,7 @@ EditorAssetInstaller::EditorAssetInstaller() {
skip_toplevel_check = memnew(CheckBox);
skip_toplevel_check->set_text(TTR("Ignore asset root"));
skip_toplevel_check->set_tooltip_text(TTR("Ignore the root directory when extracting files."));
- skip_toplevel_check->connect("toggled", callable_mp(this, &EditorAssetInstaller::_set_skip_toplevel));
+ skip_toplevel_check->connect(SceneStringName(toggled), callable_mp(this, &EditorAssetInstaller::_set_skip_toplevel));
remapping_tools->add_child(skip_toplevel_check);
remapping_tools->add_spacer();
diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp
index 3b337997e0..c076c99cd3 100644
--- a/editor/editor_audio_buses.cpp
+++ b/editor/editor_audio_buses.cpp
@@ -1262,7 +1262,7 @@ void EditorAudioBuses::_load_default_layout() {
file->set_text(String(TTR("Layout:")) + " " + layout_path.get_file());
AudioServer::get_singleton()->set_bus_layout(state);
_rebuild_buses();
- EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY);
+ EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY);
callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred();
}
@@ -1278,7 +1278,7 @@ void EditorAudioBuses::_file_dialog_callback(const String &p_string) {
file->set_text(String(TTR("Layout:")) + " " + p_string.get_file());
AudioServer::get_singleton()->set_bus_layout(state);
_rebuild_buses();
- EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY);
+ EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY);
callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred();
} else if (file_dialog->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) {
@@ -1298,7 +1298,7 @@ void EditorAudioBuses::_file_dialog_callback(const String &p_string) {
edited_path = p_string;
file->set_text(String(TTR("Layout:")) + " " + p_string.get_file());
_rebuild_buses();
- EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY);
+ EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY);
callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred();
}
}
@@ -1397,7 +1397,7 @@ void EditorAudioBuses::open_layout(const String &p_path) {
file->set_text(p_path.get_file());
AudioServer::get_singleton()->set_bus_layout(state);
_rebuild_buses();
- EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY);
+ EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY);
callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred();
}
diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp
index 32b2133f23..fb007aee28 100644
--- a/editor/editor_autoload_settings.cpp
+++ b/editor/editor_autoload_settings.cpp
@@ -88,7 +88,7 @@ void EditorAutoloadSettings::_notification(int p_what) {
}
bool EditorAutoloadSettings::_autoload_name_is_valid(const String &p_name, String *r_error) {
- if (!p_name.is_valid_identifier()) {
+ if (!p_name.is_valid_ascii_identifier()) {
if (r_error) {
*r_error = TTR("Invalid name.") + " ";
if (p_name.size() > 0 && p_name.left(1).is_numeric()) {
diff --git a/editor/editor_build_profile.cpp b/editor/editor_build_profile.cpp
index f55fbe03d8..42726a8b12 100644
--- a/editor/editor_build_profile.cpp
+++ b/editor/editor_build_profile.cpp
@@ -73,7 +73,7 @@ const bool EditorBuildProfile::build_option_disabled_by_default[BUILD_OPTION_MAX
false, // TEXT_SERVER_COMPLEX
false, // DYNAMIC_FONTS
false, // WOFF2_FONTS
- false, // GRPAHITE_FONTS
+ false, // GRAPHITE_FONTS
false, // MSDFGEN
};
@@ -91,7 +91,7 @@ const bool EditorBuildProfile::build_option_disable_values[BUILD_OPTION_MAX] = {
false, // TEXT_SERVER_COMPLEX
false, // DYNAMIC_FONTS
false, // WOFF2_FONTS
- false, // GRPAHITE_FONTS
+ false, // GRAPHITE_FONTS
false, // MSDFGEN
};
@@ -108,7 +108,7 @@ const EditorBuildProfile::BuildOptionCategory EditorBuildProfile::build_option_c
BUILD_OPTION_CATEGORY_TEXT_SERVER, // TEXT_SERVER_COMPLEX
BUILD_OPTION_CATEGORY_TEXT_SERVER, // DYNAMIC_FONTS
BUILD_OPTION_CATEGORY_TEXT_SERVER, // WOFF2_FONTS
- BUILD_OPTION_CATEGORY_TEXT_SERVER, // GRPAHITE_FONTS
+ BUILD_OPTION_CATEGORY_TEXT_SERVER, // GRAPHITE_FONTS
BUILD_OPTION_CATEGORY_TEXT_SERVER, // MSDFGEN
};
@@ -345,7 +345,7 @@ void EditorBuildProfile::_bind_methods() {
BIND_ENUM_CONSTANT(BUILD_OPTION_TEXT_SERVER_ADVANCED);
BIND_ENUM_CONSTANT(BUILD_OPTION_DYNAMIC_FONTS);
BIND_ENUM_CONSTANT(BUILD_OPTION_WOFF2_FONTS);
- BIND_ENUM_CONSTANT(BUILD_OPTION_GRPAHITE_FONTS);
+ BIND_ENUM_CONSTANT(BUILD_OPTION_GRAPHITE_FONTS);
BIND_ENUM_CONSTANT(BUILD_OPTION_MSDFGEN);
BIND_ENUM_CONSTANT(BUILD_OPTION_MAX);
diff --git a/editor/editor_build_profile.h b/editor/editor_build_profile.h
index a947365c7f..f34ab040d4 100644
--- a/editor/editor_build_profile.h
+++ b/editor/editor_build_profile.h
@@ -57,7 +57,7 @@ public:
BUILD_OPTION_TEXT_SERVER_ADVANCED,
BUILD_OPTION_DYNAMIC_FONTS,
BUILD_OPTION_WOFF2_FONTS,
- BUILD_OPTION_GRPAHITE_FONTS,
+ BUILD_OPTION_GRAPHITE_FONTS,
BUILD_OPTION_MSDFGEN,
BUILD_OPTION_MAX,
};
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index 22ff7a4509..bae9062ff1 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -95,25 +95,35 @@ String EditorFileSystemDirectory::get_file(int p_idx) const {
}
String EditorFileSystemDirectory::get_path() const {
- String p;
- const EditorFileSystemDirectory *d = this;
- while (d->parent) {
- p = d->name.path_join(p);
- d = d->parent;
+ int parents = 0;
+ const EditorFileSystemDirectory *efd = this;
+ // Determine the level of nesting.
+ while (efd->parent) {
+ parents++;
+ efd = efd->parent;
}
- return "res://" + p;
-}
+ if (parents == 0) {
+ return "res://";
+ }
-String EditorFileSystemDirectory::get_file_path(int p_idx) const {
- String file = get_file(p_idx);
- const EditorFileSystemDirectory *d = this;
- while (d->parent) {
- file = d->name.path_join(file);
- d = d->parent;
+ // Using PackedStringArray, because the path is built in reverse order.
+ PackedStringArray path_bits;
+ // Allocate an array based on nesting. It will store path bits.
+ path_bits.resize(parents + 2); // Last String is empty, so paths end with /.
+ String *path_write = path_bits.ptrw();
+ path_write[0] = "res:/";
+
+ efd = this;
+ for (int i = parents; i > 0; i--) {
+ path_write[i] = efd->name;
+ efd = efd->parent;
}
+ return String("/").join(path_bits);
+}
- return "res://" + file;
+String EditorFileSystemDirectory::get_file_path(int p_idx) const {
+ return get_path().path_join(get_file(p_idx));
}
Vector<String> EditorFileSystemDirectory::get_file_deps(int p_idx) const {
@@ -233,18 +243,27 @@ void EditorFileSystem::_first_scan_filesystem() {
first_scan_root_dir = memnew(ScannedDirectory);
first_scan_root_dir->full_path = "res://";
HashSet<String> existing_class_names;
+ HashSet<String> extensions;
ep.step(TTR("Scanning file structure..."), 0, true);
nb_files_total = _scan_new_dir(first_scan_root_dir, d);
// This loads the global class names from the scripts and ensures that even if the
// global_script_class_cache.cfg was missing or invalid, the global class names are valid in ScriptServer.
+ // At the same time, to prevent looping multiple times in all files, it looks for extensions.
ep.step(TTR("Loading global class names..."), 1, true);
- _first_scan_process_scripts(first_scan_root_dir, existing_class_names);
+ _first_scan_process_scripts(first_scan_root_dir, existing_class_names, extensions);
// Removing invalid global class to prevent having invalid paths in ScriptServer.
_remove_invalid_global_class_names(existing_class_names);
+ // Processing extensions to add new extensions or remove invalid ones.
+ // Important to do it in the first scan so custom types, new class names, custom importers, etc...
+ // from extensions are ready to go before plugins, autoloads and resources validation/importation.
+ // At this point, a restart of the editor should not be needed so we don't use the return value.
+ ep.step(TTR("Verifying GDExtensions..."), 2, true);
+ GDExtensionManager::get_singleton()->ensure_extensions_loaded(extensions);
+
// Now that all the global class names should be loaded, create autoloads and plugins.
// This is done after loading the global class names because autoloads and plugins can use
// global class names.
@@ -257,9 +276,9 @@ void EditorFileSystem::_first_scan_filesystem() {
ep.step(TTR("Starting file scan..."), 5, true);
}
-void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names) {
+void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions) {
for (ScannedDirectory *scan_sub_dir : p_scan_dir->subdirs) {
- _first_scan_process_scripts(scan_sub_dir, p_existing_class_names);
+ _first_scan_process_scripts(scan_sub_dir, p_existing_class_names, p_extensions);
}
for (const String &scan_file : p_scan_dir->files) {
@@ -275,6 +294,8 @@ void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_sca
if (!script_class_name.is_empty()) {
p_existing_class_names.insert(script_class_name);
}
+ } else if (type == SNAME("GDExtension")) {
+ p_extensions.insert(path);
}
}
}
@@ -1845,25 +1866,26 @@ void EditorFileSystem::_update_script_classes() {
return;
}
- update_script_mutex.lock();
+ {
+ MutexLock update_script_lock(update_script_mutex);
- EditorProgress *ep = nullptr;
- if (update_script_paths.size() > 1) {
- ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size()));
- }
+ EditorProgress *ep = nullptr;
+ if (update_script_paths.size() > 1) {
+ ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size()));
+ }
- int step_count = 0;
- for (const KeyValue<String, ScriptInfo> &E : update_script_paths) {
- _register_global_class_script(E.key, E.key, E.value.type, E.value.script_class_name, E.value.script_class_extends, E.value.script_class_icon_path);
- if (ep) {
- ep->step(E.value.script_class_name, step_count++, false);
+ int step_count = 0;
+ for (const KeyValue<String, ScriptInfo> &E : update_script_paths) {
+ _register_global_class_script(E.key, E.key, E.value.type, E.value.script_class_name, E.value.script_class_extends, E.value.script_class_icon_path);
+ if (ep) {
+ ep->step(E.value.script_class_name, step_count++, false);
+ }
}
- }
- memdelete_notnull(ep);
+ memdelete_notnull(ep);
- update_script_paths.clear();
- update_script_mutex.unlock();
+ update_script_paths.clear();
+ }
ScriptServer::save_global_classes();
EditorNode::get_editor_data().script_class_save_icon_paths();
@@ -1884,7 +1906,7 @@ void EditorFileSystem::_update_script_documentation() {
return;
}
- update_script_mutex.lock();
+ MutexLock update_script_lock(update_script_mutex);
EditorProgress *ep = nullptr;
if (update_script_paths_documentation.size() > 1) {
@@ -1923,7 +1945,6 @@ void EditorFileSystem::_update_script_documentation() {
memdelete_notnull(ep);
update_script_paths_documentation.clear();
- update_script_mutex.unlock();
}
void EditorFileSystem::_process_update_pending() {
@@ -1935,7 +1956,7 @@ void EditorFileSystem::_process_update_pending() {
}
void EditorFileSystem::_queue_update_script_class(const String &p_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path) {
- update_script_mutex.lock();
+ MutexLock update_script_lock(update_script_mutex);
ScriptInfo si;
si.type = p_type;
@@ -1945,8 +1966,6 @@ void EditorFileSystem::_queue_update_script_class(const String &p_path, const St
update_script_paths.insert(p_path, si);
update_script_paths_documentation.insert(p_path);
-
- update_script_mutex.unlock();
}
void EditorFileSystem::_update_scene_groups() {
@@ -1960,31 +1979,32 @@ void EditorFileSystem::_update_scene_groups() {
}
int step_count = 0;
- update_scene_mutex.lock();
- for (const String &path : update_scene_paths) {
- ProjectSettings::get_singleton()->remove_scene_groups_cache(path);
+ {
+ MutexLock update_scene_lock(update_scene_mutex);
+ for (const String &path : update_scene_paths) {
+ ProjectSettings::get_singleton()->remove_scene_groups_cache(path);
- int index = -1;
- EditorFileSystemDirectory *efd = find_file(path, &index);
+ int index = -1;
+ EditorFileSystemDirectory *efd = find_file(path, &index);
- if (!efd || index < 0) {
- // The file was removed.
- continue;
- }
+ if (!efd || index < 0) {
+ // The file was removed.
+ continue;
+ }
- const HashSet<StringName> scene_groups = PackedScene::get_scene_groups(path);
- if (!scene_groups.is_empty()) {
- ProjectSettings::get_singleton()->add_scene_groups_cache(path, scene_groups);
- }
+ const HashSet<StringName> scene_groups = PackedScene::get_scene_groups(path);
+ if (!scene_groups.is_empty()) {
+ ProjectSettings::get_singleton()->add_scene_groups_cache(path, scene_groups);
+ }
- if (ep) {
- ep->step(efd->files[index]->file, step_count++);
+ if (ep) {
+ ep->step(efd->files[index]->file, step_count++, false);
+ }
}
- }
- memdelete_notnull(ep);
- update_scene_paths.clear();
- update_scene_mutex.unlock();
+ memdelete_notnull(ep);
+ update_scene_paths.clear();
+ }
ProjectSettings::get_singleton()->save_scene_groups_cache();
}
@@ -1999,9 +2019,8 @@ void EditorFileSystem::_update_pending_scene_groups() {
}
void EditorFileSystem::_queue_update_scene_groups(const String &p_path) {
- update_scene_mutex.lock();
+ MutexLock update_scene_lock(update_scene_mutex);
update_scene_paths.insert(p_path);
- update_scene_mutex.unlock();
}
void EditorFileSystem::_get_all_scenes(EditorFileSystemDirectory *p_dir, HashSet<String> &r_list) {
@@ -2696,6 +2715,16 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) {
EditorProgress *ep = memnew(EditorProgress("reimport", TTR("(Re)Importing Assets"), p_files.size()));
+ // The method reimport_files runs on the main thread, and if VSync is enabled
+ // or Update Continuously is disabled, Main::Iteration takes longer each frame.
+ // Each EditorProgress::step can trigger a redraw, and when there are many files to import,
+ // this could lead to a slow import process, especially when the editor is unfocused.
+ // Temporarily disabling VSync and low_processor_usage_mode while reimporting fixes this.
+ const bool old_low_processor_usage_mode = OS::get_singleton()->is_in_low_processor_usage_mode();
+ const DisplayServer::VSyncMode old_vsync_mode = DisplayServer::get_singleton()->window_get_vsync_mode(DisplayServer::MAIN_WINDOW_ID);
+ OS::get_singleton()->set_low_processor_usage_mode(false);
+ DisplayServer::get_singleton()->window_set_vsync_mode(DisplayServer::VSyncMode::VSYNC_DISABLED);
+
Vector<ImportFile> reimport_files;
HashSet<String> groups_to_reimport;
@@ -2826,6 +2855,7 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) {
}
}
}
+ ep->step(TTR("Finalizing Asset Import..."), p_files.size());
ResourceUID::get_singleton()->update_cache(); // After reimporting, update the cache.
_save_filesystem_cache();
@@ -2833,6 +2863,11 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) {
memdelete_notnull(ep);
_process_update_pending();
+
+ // Revert to previous values to restore editor settings for VSync and Update Continuously.
+ OS::get_singleton()->set_low_processor_usage_mode(old_low_processor_usage_mode);
+ DisplayServer::get_singleton()->window_set_vsync_mode(old_vsync_mode);
+
importing = false;
ep = memnew(EditorProgress("reimport", TTR("(Re)Importing Assets"), p_files.size()));
@@ -2990,57 +3025,7 @@ bool EditorFileSystem::_scan_extensions() {
_scan_extensions_dir(d, extensions);
- //verify against loaded extensions
-
- Vector<String> extensions_added;
- Vector<String> extensions_removed;
-
- for (const String &E : extensions) {
- if (!GDExtensionManager::get_singleton()->is_extension_loaded(E)) {
- extensions_added.push_back(E);
- }
- }
-
- Vector<String> loaded_extensions = GDExtensionManager::get_singleton()->get_loaded_extensions();
- for (int i = 0; i < loaded_extensions.size(); i++) {
- if (!extensions.has(loaded_extensions[i])) {
- // The extension may not have a .gdextension file.
- if (!FileAccess::exists(loaded_extensions[i])) {
- extensions_removed.push_back(loaded_extensions[i]);
- }
- }
- }
-
- String extension_list_config_file = GDExtension::get_extension_list_config_file();
- if (extensions.size()) {
- if (extensions_added.size() || extensions_removed.size()) { //extensions were added or removed
- Ref<FileAccess> f = FileAccess::open(extension_list_config_file, FileAccess::WRITE);
- for (const String &E : extensions) {
- f->store_line(E);
- }
- }
- } else {
- if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) { //extensions were removed
- Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
- da->remove(extension_list_config_file);
- }
- }
-
- bool needs_restart = false;
- for (int i = 0; i < extensions_added.size(); i++) {
- GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extensions_added[i]);
- if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
- needs_restart = true;
- }
- }
- for (int i = 0; i < extensions_removed.size(); i++) {
- GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extensions_removed[i]);
- if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
- needs_restart = true;
- }
- }
-
- return needs_restart;
+ return GDExtensionManager::get_singleton()->ensure_extensions_loaded(extensions);
}
void EditorFileSystem::_bind_methods() {
diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h
index 1bc24416eb..ca4a64bfac 100644
--- a/editor/editor_file_system.h
+++ b/editor/editor_file_system.h
@@ -187,7 +187,7 @@ class EditorFileSystem : public Node {
void _scan_filesystem();
void _first_scan_filesystem();
- void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names);
+ void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions);
HashSet<String> late_update_files;
diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp
index ff5bc6ba87..eb97337b37 100644
--- a/editor/editor_help_search.cpp
+++ b/editor/editor_help_search.cpp
@@ -134,18 +134,38 @@ void EditorHelpSearch::_native_action_cb(const String &p_item_string) {
}
void EditorHelpSearch::_update_results() {
- String term = search_box->get_text();
+ const String term = search_box->get_text().strip_edges();
int search_flags = filter_combo->get_selected_id();
- if (case_sensitive_button->is_pressed()) {
- search_flags |= SEARCH_CASE_SENSITIVE;
- }
- if (hierarchy_button->is_pressed()) {
- search_flags |= SEARCH_SHOW_HIERARCHY;
- }
- search = Ref<Runner>(memnew(Runner(results_tree, results_tree, &tree_cache, term, search_flags)));
- set_process(true);
+ // Process separately if term is not short, or is "@" for annotations.
+ if (term.length() > 1 || term == "@") {
+ case_sensitive_button->set_disabled(false);
+ hierarchy_button->set_disabled(false);
+
+ if (case_sensitive_button->is_pressed()) {
+ search_flags |= SEARCH_CASE_SENSITIVE;
+ }
+ if (hierarchy_button->is_pressed()) {
+ search_flags |= SEARCH_SHOW_HIERARCHY;
+ }
+
+ search = Ref<Runner>(memnew(Runner(results_tree, results_tree, &tree_cache, term, search_flags)));
+
+ // Clear old search flags to force rebuild on short term.
+ old_search_flags = 0;
+ set_process(true);
+ } else {
+ // Disable hierarchy and case sensitive options, not used for short searches.
+ case_sensitive_button->set_disabled(true);
+ hierarchy_button->set_disabled(true);
+
+ // Always show hierarchy for short searches.
+ search = Ref<Runner>(memnew(Runner(results_tree, results_tree, &tree_cache, term, search_flags | SEARCH_SHOW_HIERARCHY)));
+
+ old_search_flags = search_flags;
+ set_process(true);
+ }
}
void EditorHelpSearch::_search_box_gui_input(const Ref<InputEvent> &p_event) {
@@ -205,6 +225,8 @@ void EditorHelpSearch::_notification(int p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_visible()) {
tree_cache.clear();
+ results_tree->get_vscroll_bar()->set_value(0);
+ search = Ref<Runner>();
callable_mp(results_tree, &Tree::clear).call_deferred(); // Wait for the Tree's mouse event propagation.
get_ok_button()->set_disabled(true);
EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "search_help", Rect2(get_position(), get_size()));
@@ -278,6 +300,7 @@ void EditorHelpSearch::popup_dialog(const String &p_term) {
popup_centered_ratio(0.5F);
}
+ old_search_flags = 0;
if (p_term.is_empty()) {
search_box->clear();
} else {
@@ -401,6 +424,237 @@ bool EditorHelpSearch::Runner::_is_class_disabled_by_feature_profile(const Strin
return false;
}
+bool EditorHelpSearch::Runner::_fill() {
+ bool phase_done = false;
+ switch (phase) {
+ case PHASE_MATCH_CLASSES_INIT:
+ phase_done = _phase_fill_classes_init();
+ break;
+ case PHASE_MATCH_CLASSES:
+ phase_done = _phase_fill_classes();
+ break;
+ case PHASE_CLASS_ITEMS_INIT:
+ case PHASE_CLASS_ITEMS:
+ phase_done = true;
+ break;
+ case PHASE_MEMBER_ITEMS_INIT:
+ phase_done = _phase_fill_member_items_init();
+ break;
+ case PHASE_MEMBER_ITEMS:
+ phase_done = _phase_fill_member_items();
+ break;
+ case PHASE_SELECT_MATCH:
+ phase_done = _phase_select_match();
+ break;
+ case PHASE_MAX:
+ return true;
+ default:
+ WARN_PRINT("Invalid or unhandled phase in EditorHelpSearch::Runner, aborting search.");
+ return true;
+ }
+
+ if (phase_done) {
+ phase++;
+ }
+ return false;
+}
+
+bool EditorHelpSearch::Runner::_phase_fill_classes_init() {
+ // Initialize fill.
+ iterator_stack.clear();
+ matched_classes.clear();
+ matched_item = nullptr;
+ match_highest_score = 0;
+
+ // Initialize stack of iterators to fill, in reverse.
+ iterator_stack.push_back(EditorHelp::get_doc_data()->inheriting[""].back());
+
+ return true;
+}
+
+bool EditorHelpSearch::Runner::_phase_fill_classes() {
+ if (iterator_stack.is_empty()) {
+ return true;
+ }
+
+ if (iterator_stack[iterator_stack.size() - 1]) {
+ DocData::ClassDoc *class_doc = EditorHelp::get_doc_data()->class_list.getptr(iterator_stack[iterator_stack.size() - 1]->get());
+
+ // Decrement stack.
+ iterator_stack[iterator_stack.size() - 1] = iterator_stack[iterator_stack.size() - 1]->prev();
+
+ // Drop last element of stack if empty.
+ if (!iterator_stack[iterator_stack.size() - 1]) {
+ iterator_stack.resize(iterator_stack.size() - 1);
+ }
+
+ if (!class_doc || class_doc->name.is_empty()) {
+ return false;
+ }
+
+ // If class matches the flags, add it to the matched stack.
+ const bool class_matched =
+ (search_flags & SEARCH_CLASSES) ||
+ ((search_flags & SEARCH_CONSTRUCTORS) && !class_doc->constructors.is_empty()) ||
+ ((search_flags & SEARCH_METHODS) && !class_doc->methods.is_empty()) ||
+ ((search_flags & SEARCH_OPERATORS) && !class_doc->operators.is_empty()) ||
+ ((search_flags & SEARCH_SIGNALS) && !class_doc->signals.is_empty()) ||
+ ((search_flags & SEARCH_CONSTANTS) && !class_doc->constants.is_empty()) ||
+ ((search_flags & SEARCH_PROPERTIES) && !class_doc->properties.is_empty()) ||
+ ((search_flags & SEARCH_THEME_ITEMS) && !class_doc->theme_properties.is_empty()) ||
+ ((search_flags & SEARCH_ANNOTATIONS) && !class_doc->annotations.is_empty());
+
+ if (class_matched) {
+ if (term.is_empty() || class_doc->name.containsn(term)) {
+ matched_classes.push_back(Pair<DocData::ClassDoc *, String>(class_doc, String()));
+ } else if (String keyword = _match_keywords(term, class_doc->keywords); !keyword.is_empty()) {
+ matched_classes.push_back(Pair<DocData::ClassDoc *, String>(class_doc, keyword));
+ }
+ }
+
+ // Add inheriting classes, in reverse.
+ if (class_doc && EditorHelp::get_doc_data()->inheriting.has(class_doc->name)) {
+ iterator_stack.push_back(EditorHelp::get_doc_data()->inheriting[class_doc->name].back());
+ }
+
+ return false;
+ }
+
+ // Drop last element of stack if empty.
+ if (!iterator_stack[iterator_stack.size() - 1]) {
+ iterator_stack.resize(iterator_stack.size() - 1);
+ }
+
+ return iterator_stack.is_empty();
+}
+
+bool EditorHelpSearch::Runner::_phase_fill_member_items_init() {
+ // Prepare tree.
+ class_items.clear();
+ _populate_cache();
+
+ return true;
+}
+
+TreeItem *EditorHelpSearch::Runner::_create_category_item(TreeItem *p_parent, const String &p_class, const StringName &p_icon, const String &p_metatype, const String &p_text) {
+ const String item_meta = "class_" + p_metatype + ":" + p_class;
+
+ TreeItem *item = nullptr;
+ if (_find_or_create_item(p_parent, item_meta, item)) {
+ item->set_icon(0, ui_service->get_editor_theme_icon(p_icon));
+ item->set_text(0, p_text);
+ item->set_metadata(0, item_meta);
+ }
+ item->set_collapsed(true);
+
+ return item;
+}
+
+bool EditorHelpSearch::Runner::_phase_fill_member_items() {
+ if (matched_classes.is_empty()) {
+ return true;
+ }
+
+ // Pop working item from stack.
+ Pair<DocData::ClassDoc *, String> match = matched_classes[matched_classes.size() - 1];
+ DocData::ClassDoc *class_doc = match.first;
+ const String &keyword = match.second;
+ matched_classes.resize(matched_classes.size() - 1);
+
+ if (class_doc) {
+ TreeItem *item = _create_class_hierarchy(class_doc, keyword, !(search_flags & SEARCH_CLASSES));
+
+ // If the class has no inheriting classes, fold its item.
+ item->set_collapsed(!item->get_first_child());
+
+ if (search_flags & SEARCH_CLASSES) {
+ item->clear_custom_color(0);
+ item->clear_custom_color(1);
+ } else {
+ item->set_custom_color(0, disabled_color);
+ item->set_custom_color(1, disabled_color);
+ }
+
+ // Create common header if required.
+ const bool search_all = (search_flags & SEARCH_ALL) == SEARCH_ALL;
+
+ if ((search_flags & SEARCH_CONSTRUCTORS) && !class_doc->constructors.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberConstructor"), TTRC("Constructors"), "constructors");
+ }
+ for (const DocData::MethodDoc &constructor_doc : class_doc->constructors) {
+ _create_constructor_item(parent_item, class_doc, &constructor_doc);
+ }
+ }
+ if ((search_flags & SEARCH_METHODS) && !class_doc->methods.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberMethod"), TTRC("Methods"), "methods");
+ }
+ for (const DocData::MethodDoc &method_doc : class_doc->methods) {
+ _create_method_item(parent_item, class_doc, &method_doc);
+ }
+ }
+ if ((search_flags & SEARCH_OPERATORS) && !class_doc->operators.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberOperator"), TTRC("Operators"), "operators");
+ }
+ for (const DocData::MethodDoc &operator_doc : class_doc->operators) {
+ _create_operator_item(parent_item, class_doc, &operator_doc);
+ }
+ }
+ if ((search_flags & SEARCH_SIGNALS) && !class_doc->signals.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberSignal"), TTRC("Signals"), "signals");
+ }
+ for (const DocData::MethodDoc &signal_doc : class_doc->signals) {
+ _create_signal_item(parent_item, class_doc, &signal_doc);
+ }
+ }
+ if ((search_flags & SEARCH_CONSTANTS) && !class_doc->constants.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberConstant"), TTRC("Constants"), "constants");
+ }
+ for (const DocData::ConstantDoc &constant_doc : class_doc->constants) {
+ _create_constant_item(parent_item, class_doc, &constant_doc);
+ }
+ }
+ if ((search_flags & SEARCH_PROPERTIES) && !class_doc->properties.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberProperty"), TTRC("Prtoperties"), "propertiess");
+ }
+ for (const DocData::PropertyDoc &property_doc : class_doc->properties) {
+ _create_property_item(parent_item, class_doc, &property_doc);
+ }
+ }
+ if ((search_flags & SEARCH_THEME_ITEMS) && !class_doc->theme_properties.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberTheme"), TTRC("Theme Properties"), "theme_items");
+ }
+ for (const DocData::ThemeItemDoc &theme_property_doc : class_doc->theme_properties) {
+ _create_theme_property_item(parent_item, class_doc, &theme_property_doc);
+ }
+ }
+ if ((search_flags & SEARCH_ANNOTATIONS) && !class_doc->annotations.is_empty()) {
+ TreeItem *parent_item = item;
+ if (search_all) {
+ parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberAnnotation"), TTRC("Annotations"), "annotations");
+ }
+ for (const DocData::MethodDoc &annotation_doc : class_doc->annotations) {
+ _create_annotation_item(parent_item, class_doc, &annotation_doc);
+ }
+ }
+ }
+
+ return matched_classes.is_empty();
+}
+
bool EditorHelpSearch::Runner::_slice() {
bool phase_done = false;
switch (phase) {
@@ -430,7 +684,7 @@ bool EditorHelpSearch::Runner::_slice() {
default:
WARN_PRINT("Invalid or unhandled phase in EditorHelpSearch::Runner, aborting search.");
return true;
- };
+ }
if (phase_done) {
phase++;
@@ -450,9 +704,11 @@ bool EditorHelpSearch::Runner::_phase_match_classes_init() {
matched_item = nullptr;
match_highest_score = 0;
- terms = term.split_spaces();
- if (terms.is_empty()) {
- terms.append(term);
+ if (!term.is_empty()) {
+ terms = term.split_spaces();
+ if (terms.is_empty()) {
+ terms.append(term);
+ }
}
return true;
@@ -480,78 +736,71 @@ bool EditorHelpSearch::Runner::_phase_match_classes() {
// Match class name.
if (search_flags & SEARCH_CLASSES) {
- // If the search term is empty, add any classes which are not script docs or which don't start with
- // a double-quotation. This will ensure that only C++ classes and explicitly named classes will
- // be added.
- match.name = (term.is_empty() && (!class_doc->is_script_doc || class_doc->name[0] != '\"')) || _match_string(term, class_doc->name);
+ match.name = _match_string(term, class_doc->name);
match.keyword = _match_keywords(term, class_doc->keywords);
}
- // Match members only if the term is long enough, to avoid slow performance from building a large tree.
- // Make an exception for annotations, since there are not that many of them.
- if (term.length() > 1 || term == "@") {
- if (search_flags & SEARCH_CONSTRUCTORS) {
- _match_method_name_and_push_back(class_doc->constructors, &match.constructors);
- }
- if (search_flags & SEARCH_METHODS) {
- _match_method_name_and_push_back(class_doc->methods, &match.methods);
- }
- if (search_flags & SEARCH_OPERATORS) {
- _match_method_name_and_push_back(class_doc->operators, &match.operators);
- }
- if (search_flags & SEARCH_SIGNALS) {
- for (int i = 0; i < class_doc->signals.size(); i++) {
- MemberMatch<DocData::MethodDoc> signal;
- signal.name = _all_terms_in_name(class_doc->signals[i].name);
- signal.keyword = _match_keywords_in_all_terms(class_doc->signals[i].keywords);
- if (signal.name || !signal.keyword.is_empty()) {
- signal.doc = const_cast<DocData::MethodDoc *>(&class_doc->signals[i]);
- match.signals.push_back(signal);
- }
+ if (search_flags & SEARCH_CONSTRUCTORS) {
+ _match_method_name_and_push_back(class_doc->constructors, &match.constructors);
+ }
+ if (search_flags & SEARCH_METHODS) {
+ _match_method_name_and_push_back(class_doc->methods, &match.methods);
+ }
+ if (search_flags & SEARCH_OPERATORS) {
+ _match_method_name_and_push_back(class_doc->operators, &match.operators);
+ }
+ if (search_flags & SEARCH_SIGNALS) {
+ for (const DocData::MethodDoc &signal_doc : class_doc->signals) {
+ MemberMatch<DocData::MethodDoc> signal;
+ signal.name = _all_terms_in_name(signal_doc.name);
+ signal.keyword = _match_keywords_in_all_terms(signal_doc.keywords);
+ if (signal.name || !signal.keyword.is_empty()) {
+ signal.doc = &signal_doc;
+ match.signals.push_back(signal);
}
}
- if (search_flags & SEARCH_CONSTANTS) {
- for (int i = 0; i < class_doc->constants.size(); i++) {
- MemberMatch<DocData::ConstantDoc> constant;
- constant.name = _all_terms_in_name(class_doc->constants[i].name);
- constant.keyword = _match_keywords_in_all_terms(class_doc->constants[i].keywords);
- if (constant.name || !constant.keyword.is_empty()) {
- constant.doc = const_cast<DocData::ConstantDoc *>(&class_doc->constants[i]);
- match.constants.push_back(constant);
- }
+ }
+ if (search_flags & SEARCH_CONSTANTS) {
+ for (const DocData::ConstantDoc &constant_doc : class_doc->constants) {
+ MemberMatch<DocData::ConstantDoc> constant;
+ constant.name = _all_terms_in_name(constant_doc.name);
+ constant.keyword = _match_keywords_in_all_terms(constant_doc.keywords);
+ if (constant.name || !constant.keyword.is_empty()) {
+ constant.doc = &constant_doc;
+ match.constants.push_back(constant);
}
}
- if (search_flags & SEARCH_PROPERTIES) {
- for (int i = 0; i < class_doc->properties.size(); i++) {
- MemberMatch<DocData::PropertyDoc> property;
- property.name = _all_terms_in_name(class_doc->properties[i].name);
- property.keyword = _match_keywords_in_all_terms(class_doc->properties[i].keywords);
- if (property.name || !property.keyword.is_empty()) {
- property.doc = const_cast<DocData::PropertyDoc *>(&class_doc->properties[i]);
- match.properties.push_back(property);
- }
+ }
+ if (search_flags & SEARCH_PROPERTIES) {
+ for (const DocData::PropertyDoc &property_doc : class_doc->properties) {
+ MemberMatch<DocData::PropertyDoc> property;
+ property.name = _all_terms_in_name(property_doc.name);
+ property.keyword = _match_keywords_in_all_terms(property_doc.keywords);
+ if (property.name || !property.keyword.is_empty()) {
+ property.doc = &property_doc;
+ match.properties.push_back(property);
}
}
- if (search_flags & SEARCH_THEME_ITEMS) {
- for (int i = 0; i < class_doc->theme_properties.size(); i++) {
- MemberMatch<DocData::ThemeItemDoc> theme_property;
- theme_property.name = _all_terms_in_name(class_doc->theme_properties[i].name);
- theme_property.keyword = _match_keywords_in_all_terms(class_doc->theme_properties[i].keywords);
- if (theme_property.name || !theme_property.keyword.is_empty()) {
- theme_property.doc = const_cast<DocData::ThemeItemDoc *>(&class_doc->theme_properties[i]);
- match.theme_properties.push_back(theme_property);
- }
+ }
+ if (search_flags & SEARCH_THEME_ITEMS) {
+ for (const DocData::ThemeItemDoc &theme_property_doc : class_doc->theme_properties) {
+ MemberMatch<DocData::ThemeItemDoc> theme_property;
+ theme_property.name = _all_terms_in_name(theme_property_doc.name);
+ theme_property.keyword = _match_keywords_in_all_terms(theme_property_doc.keywords);
+ if (theme_property.name || !theme_property.keyword.is_empty()) {
+ theme_property.doc = &theme_property_doc;
+ match.theme_properties.push_back(theme_property);
}
}
- if (search_flags & SEARCH_ANNOTATIONS) {
- for (int i = 0; i < class_doc->annotations.size(); i++) {
- MemberMatch<DocData::MethodDoc> annotation;
- annotation.name = _all_terms_in_name(class_doc->annotations[i].name);
- annotation.keyword = _match_keywords_in_all_terms(class_doc->annotations[i].keywords);
- if (annotation.name || !annotation.keyword.is_empty()) {
- annotation.doc = const_cast<DocData::MethodDoc *>(&class_doc->annotations[i]);
- match.annotations.push_back(annotation);
- }
+ }
+ if (search_flags & SEARCH_ANNOTATIONS) {
+ for (const DocData::MethodDoc &annotation_doc : class_doc->annotations) {
+ MemberMatch<DocData::MethodDoc> annotation;
+ annotation.name = _all_terms_in_name(annotation_doc.name);
+ annotation.keyword = _match_keywords_in_all_terms(annotation_doc.keywords);
+ if (annotation.name || !annotation.keyword.is_empty()) {
+ annotation.doc = &annotation_doc;
+ match.annotations.push_back(annotation);
}
}
}
@@ -564,9 +813,11 @@ bool EditorHelpSearch::Runner::_phase_match_classes() {
}
if (!iterator_stack.is_empty()) {
+ // Iterate on stack.
if (iterator_stack[iterator_stack.size() - 1]) {
iterator_stack[iterator_stack.size() - 1] = iterator_stack[iterator_stack.size() - 1]->next();
}
+ // Drop last element of stack.
if (!iterator_stack[iterator_stack.size() - 1]) {
iterator_stack.resize(iterator_stack.size() - 1);
}
@@ -661,36 +912,32 @@ bool EditorHelpSearch::Runner::_phase_member_items() {
return false;
}
+ // Pick appropriate parent item if showing hierarchy, otherwise pick root.
TreeItem *parent_item = (search_flags & SEARCH_SHOW_HIERARCHY) ? class_items[match.doc->name] : root_item;
- bool constructor_created = false;
- for (int i = 0; i < match.methods.size(); i++) {
- String text = match.methods[i].doc->name;
- if (!constructor_created) {
- if (match.doc->name == match.methods[i].doc->name) {
- text += " " + TTR("(constructors)");
- constructor_created = true;
- }
- } else {
- if (match.doc->name == match.methods[i].doc->name) {
- continue;
- }
- }
- _create_method_item(parent_item, match.doc, text, match.methods[i]);
+
+ for (const MemberMatch<DocData::MethodDoc> &constructor_item : match.constructors) {
+ _create_constructor_item(parent_item, match.doc, constructor_item);
}
- for (int i = 0; i < match.signals.size(); i++) {
- _create_signal_item(parent_item, match.doc, match.signals[i]);
+ for (const MemberMatch<DocData::MethodDoc> &method_item : match.methods) {
+ _create_method_item(parent_item, match.doc, method_item);
}
- for (int i = 0; i < match.constants.size(); i++) {
- _create_constant_item(parent_item, match.doc, match.constants[i]);
+ for (const MemberMatch<DocData::MethodDoc> &operator_item : match.operators) {
+ _create_operator_item(parent_item, match.doc, operator_item);
}
- for (int i = 0; i < match.properties.size(); i++) {
- _create_property_item(parent_item, match.doc, match.properties[i]);
+ for (const MemberMatch<DocData::MethodDoc> &signal_item : match.signals) {
+ _create_signal_item(parent_item, match.doc, signal_item);
}
- for (int i = 0; i < match.theme_properties.size(); i++) {
- _create_theme_property_item(parent_item, match.doc, match.theme_properties[i]);
+ for (const MemberMatch<DocData::ConstantDoc> &constant_item : match.constants) {
+ _create_constant_item(parent_item, match.doc, constant_item);
}
- for (int i = 0; i < match.annotations.size(); i++) {
- _create_annotation_item(parent_item, match.doc, match.annotations[i]);
+ for (const MemberMatch<DocData::PropertyDoc> &property_item : match.properties) {
+ _create_property_item(parent_item, match.doc, property_item);
+ }
+ for (const MemberMatch<DocData::ThemeItemDoc> &theme_property_item : match.theme_properties) {
+ _create_theme_property_item(parent_item, match.doc, theme_property_item);
+ }
+ for (const MemberMatch<DocData::MethodDoc> &annotation_item : match.annotations) {
+ _create_annotation_item(parent_item, match.doc, annotation_item);
}
++iterator_match;
@@ -704,7 +951,7 @@ bool EditorHelpSearch::Runner::_phase_select_match() {
return true;
}
-void EditorHelpSearch::Runner::_match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, Vector<MemberMatch<DocData::MethodDoc>> *r_match_methods) {
+void EditorHelpSearch::Runner::_match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, LocalVector<MemberMatch<DocData::MethodDoc>> *r_match_methods) {
// Constructors, Methods, Operators...
for (int i = 0; i < p_methods.size(); i++) {
String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? p_methods[i].name : p_methods[i].name.to_lower();
@@ -765,12 +1012,12 @@ void EditorHelpSearch::Runner::_match_item(TreeItem *p_item, const String &p_tex
return;
}
- float inverse_length = 1.f / float(p_text.length());
+ float inverse_length = 1.0f / float(p_text.length());
// Favor types where search term is a substring close to the start of the type.
float w = 0.5f;
int pos = p_text.findn(term);
- float score = (pos > -1) ? 1.0f - w * MIN(1, 3 * pos * inverse_length) : MAX(0.f, .9f - w);
+ float score = (pos > -1) ? 1.0f - w * MIN(1, 3 * pos * inverse_length) : MAX(0.0f, 0.9f - w);
// Favor shorter items: they resemble the search term more.
w = 0.1f;
@@ -781,7 +1028,8 @@ void EditorHelpSearch::Runner::_match_item(TreeItem *p_item, const String &p_tex
score *= 0.9f;
}
- if (match_highest_score == 0 || score > match_highest_score) {
+ // Replace current match if term is short as we are searching in reverse.
+ if (match_highest_score == 0 || score > match_highest_score || (score == match_highest_score && term.length() == 1)) {
matched_item = p_item;
match_highest_score = score;
}
@@ -820,6 +1068,29 @@ String EditorHelpSearch::Runner::_build_keywords_tooltip(const String &p_keyword
return tooltip.left(-2);
}
+TreeItem *EditorHelpSearch::Runner::_create_class_hierarchy(const DocData::ClassDoc *p_class_doc, const String &p_matching_keyword, bool p_gray) {
+ if (p_class_doc->name.is_empty()) {
+ return nullptr;
+ }
+ if (TreeItem **found = class_items.getptr(p_class_doc->name)) {
+ return *found;
+ }
+
+ // Ensure parent nodes are created first.
+ TreeItem *parent_item = root_item;
+ if (!p_class_doc->inherits.is_empty()) {
+ if (class_items.has(p_class_doc->inherits)) {
+ parent_item = class_items[p_class_doc->inherits];
+ } else if (const DocData::ClassDoc *found = EditorHelp::get_doc_data()->class_list.getptr(p_class_doc->inherits)) {
+ parent_item = _create_class_hierarchy(found, String(), true);
+ }
+ }
+
+ TreeItem *class_item = _create_class_item(parent_item, p_class_doc, p_gray, p_matching_keyword);
+ class_items[p_class_doc->name] = class_item;
+ return class_item;
+}
+
TreeItem *EditorHelpSearch::Runner::_create_class_hierarchy(const ClassMatch &p_match) {
if (p_match.doc->name.is_empty()) {
return nullptr;
@@ -887,6 +1158,8 @@ TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const
item->add_button(0, warning_icon, 0, false, TTR("This class is marked as experimental."));
}
}
+ // Cached item might be collapsed.
+ item->set_collapsed(false);
if (p_gray) {
item->set_custom_color(0, disabled_color);
@@ -902,7 +1175,9 @@ TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const
item->set_text(0, p_doc->name + " - " + TTR(vformat("Matches the \"%s\" keyword.", p_matching_keyword)));
}
- _match_item(item, p_doc->name);
+ if (!term.is_empty()) {
+ _match_item(item, p_doc->name);
+ }
for (const String &keyword : p_doc->keywords.split(",")) {
_match_item(item, keyword.strip_edges(), true);
}
@@ -910,44 +1185,73 @@ TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const
return item;
}
-TreeItem *EditorHelpSearch::Runner::_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const MemberMatch<DocData::MethodDoc> &p_match) {
+TreeItem *EditorHelpSearch::Runner::_create_constructor_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) {
+ String tooltip = p_class_doc->name + "(";
+ String text = p_class_doc->name + "(";
+ for (int i = 0; i < p_match.doc->arguments.size(); i++) {
+ const DocData::ArgumentDoc &arg = p_match.doc->arguments[i];
+ tooltip += arg.type + " " + arg.name;
+ text += arg.type;
+ if (!arg.default_value.is_empty()) {
+ tooltip += " = " + arg.default_value;
+ }
+ if (i < p_match.doc->arguments.size() - 1) {
+ tooltip += ", ";
+ text += ", ";
+ }
+ }
+ tooltip += ")";
+ tooltip += _build_keywords_tooltip(p_match.doc->keywords);
+ text += ")";
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberConstructor"), p_match.doc->name, text, TTRC("Constructor"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+}
+
+TreeItem *EditorHelpSearch::Runner::_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) {
String tooltip = _build_method_tooltip(p_class_doc, p_match.doc);
- return _create_member_item(p_parent, p_class_doc->name, "MemberMethod", p_match.doc->name, p_text, TTRC("Method"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberMethod"), p_match.doc->name, p_match.doc->name, TTRC("Method"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+}
+
+TreeItem *EditorHelpSearch::Runner::_create_operator_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) {
+ String tooltip = _build_method_tooltip(p_class_doc, p_match.doc);
+ String text = p_match.doc->name;
+ if (!p_match.doc->arguments.is_empty()) {
+ text += "(" + p_match.doc->arguments[0].type + ")";
+ }
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberOperator"), p_match.doc->name, text, TTRC("Operator"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
}
TreeItem *EditorHelpSearch::Runner::_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) {
String tooltip = _build_method_tooltip(p_class_doc, p_match.doc);
- return _create_member_item(p_parent, p_class_doc->name, "MemberSignal", p_match.doc->name, p_match.doc->name, TTRC("Signal"), "signal", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberSignal"), p_match.doc->name, p_match.doc->name, TTRC("Signal"), "signal", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
}
TreeItem *EditorHelpSearch::Runner::_create_annotation_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) {
String tooltip = _build_method_tooltip(p_class_doc, p_match.doc);
// Hide the redundant leading @ symbol.
String text = p_match.doc->name.substr(1);
- return _create_member_item(p_parent, p_class_doc->name, "MemberAnnotation", p_match.doc->name, text, TTRC("Annotation"), "annotation", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberAnnotation"), p_match.doc->name, text, TTRC("Annotation"), "annotation", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
}
TreeItem *EditorHelpSearch::Runner::_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ConstantDoc> &p_match) {
String tooltip = p_class_doc->name + "." + p_match.doc->name;
tooltip += _build_keywords_tooltip(p_match.doc->keywords);
- return _create_member_item(p_parent, p_class_doc->name, "MemberConstant", p_match.doc->name, p_match.doc->name, TTRC("Constant"), "constant", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberConstant"), p_match.doc->name, p_match.doc->name, TTRC("Constant"), "constant", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
}
TreeItem *EditorHelpSearch::Runner::_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::PropertyDoc> &p_match) {
String tooltip = p_match.doc->type + " " + p_class_doc->name + "." + p_match.doc->name;
tooltip += "\n " + p_class_doc->name + "." + p_match.doc->setter + "(value) setter";
tooltip += "\n " + p_class_doc->name + "." + p_match.doc->getter + "() getter";
- tooltip += _build_keywords_tooltip(p_match.doc->keywords);
- return _create_member_item(p_parent, p_class_doc->name, "MemberProperty", p_match.doc->name, p_match.doc->name, TTRC("Property"), "property", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberProperty"), p_match.doc->name, p_match.doc->name, TTRC("Property"), "property", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword);
}
TreeItem *EditorHelpSearch::Runner::_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ThemeItemDoc> &p_match) {
String tooltip = p_match.doc->type + " " + p_class_doc->name + "." + p_match.doc->name;
tooltip += _build_keywords_tooltip(p_match.doc->keywords);
- return _create_member_item(p_parent, p_class_doc->name, "MemberTheme", p_match.doc->name, p_match.doc->name, TTRC("Theme Property"), "theme_item", p_match.doc->keywords, tooltip, false, false, p_match.name ? String() : p_match.keyword);
+ return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberTheme"), p_match.doc->name, p_match.doc->name, TTRC("Theme Property"), "theme_item", p_match.doc->keywords, tooltip, false, false, p_match.name ? String() : p_match.keyword);
}
-TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool p_is_deprecated, bool p_is_experimental, const String &p_matching_keyword) {
+TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, const String &p_class_name, const StringName &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool p_is_deprecated, bool p_is_experimental, const String &p_matching_keyword) {
const String item_meta = "class_" + p_metatype + ":" + p_class_name + ":" + p_name;
TreeItem *item = nullptr;
@@ -978,7 +1282,10 @@ TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, cons
}
item->set_text(0, text);
- _match_item(item, p_name);
+ // Don't match member items for short searches.
+ if (term.length() > 1 || term == "@") {
+ _match_item(item, p_name);
+ }
for (const String &keyword : p_keywords.split(",")) {
_match_item(item, keyword.strip_edges(), true);
}
@@ -989,9 +1296,17 @@ TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, cons
bool EditorHelpSearch::Runner::work(uint64_t slot) {
// Return true when the search has been completed, otherwise false.
const uint64_t until = OS::get_singleton()->get_ticks_usec() + slot;
- while (!_slice()) {
- if (OS::get_singleton()->get_ticks_usec() > until) {
- return false;
+ if (term.length() > 1 || term == "@") {
+ while (!_slice()) {
+ if (OS::get_singleton()->get_ticks_usec() > until) {
+ return false;
+ }
+ }
+ } else {
+ while (!_fill()) {
+ if (OS::get_singleton()->get_ticks_usec() > until) {
+ return false;
+ }
}
}
return true;
@@ -1001,7 +1316,7 @@ EditorHelpSearch::Runner::Runner(Control *p_icon_service, Tree *p_results_tree,
ui_service(p_icon_service),
results_tree(p_results_tree),
tree_cache(p_tree_cache),
- term((p_search_flags & SEARCH_CASE_SENSITIVE) == 0 ? p_term.strip_edges().to_lower() : p_term.strip_edges()),
+ term((p_search_flags & SEARCH_CASE_SENSITIVE) == 0 ? p_term.to_lower() : p_term),
search_flags(p_search_flags),
disabled_color(ui_service->get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor))) {
}
diff --git a/editor/editor_help_search.h b/editor/editor_help_search.h
index 58061dae4c..b8b3c26b41 100644
--- a/editor/editor_help_search.h
+++ b/editor/editor_help_search.h
@@ -63,6 +63,7 @@ class EditorHelpSearch : public ConfirmationDialog {
Tree *results_tree = nullptr;
bool old_search = false;
String old_term;
+ int old_search_flags = 0;
class Runner;
Ref<Runner> search;
@@ -119,26 +120,30 @@ class EditorHelpSearch::Runner : public RefCounted {
template <typename T>
struct MemberMatch {
- T *doc = nullptr;
+ const T *doc = nullptr;
bool name = false;
String keyword;
+
+ MemberMatch() {}
+ MemberMatch(const T *p_doc) :
+ doc(p_doc) {}
};
struct ClassMatch {
- DocData::ClassDoc *doc = nullptr;
+ const DocData::ClassDoc *doc = nullptr;
bool name = false;
String keyword;
- Vector<MemberMatch<DocData::MethodDoc>> constructors;
- Vector<MemberMatch<DocData::MethodDoc>> methods;
- Vector<MemberMatch<DocData::MethodDoc>> operators;
- Vector<MemberMatch<DocData::MethodDoc>> signals;
- Vector<MemberMatch<DocData::ConstantDoc>> constants;
- Vector<MemberMatch<DocData::PropertyDoc>> properties;
- Vector<MemberMatch<DocData::ThemeItemDoc>> theme_properties;
- Vector<MemberMatch<DocData::MethodDoc>> annotations;
+ LocalVector<MemberMatch<DocData::MethodDoc>> constructors;
+ LocalVector<MemberMatch<DocData::MethodDoc>> methods;
+ LocalVector<MemberMatch<DocData::MethodDoc>> operators;
+ LocalVector<MemberMatch<DocData::MethodDoc>> signals;
+ LocalVector<MemberMatch<DocData::ConstantDoc>> constants;
+ LocalVector<MemberMatch<DocData::PropertyDoc>> properties;
+ LocalVector<MemberMatch<DocData::ThemeItemDoc>> theme_properties;
+ LocalVector<MemberMatch<DocData::MethodDoc>> annotations;
bool required() {
- return name || !keyword.is_empty() || methods.size() || signals.size() || constants.size() || properties.size() || theme_properties.size() || annotations.size();
+ return name || !keyword.is_empty() || !constructors.is_empty() || !methods.is_empty() || !operators.is_empty() || !signals.is_empty() || !constants.is_empty() || !properties.is_empty() || !theme_properties.is_empty() || !annotations.is_empty();
}
};
@@ -155,6 +160,7 @@ class EditorHelpSearch::Runner : public RefCounted {
LocalVector<RBSet<String, NaturalNoCaseComparator>::Element *> iterator_stack;
HashMap<String, ClassMatch> matches;
HashMap<String, ClassMatch>::Iterator iterator_match;
+ LocalVector<Pair<DocData::ClassDoc *, String>> matched_classes;
TreeItem *root_item = nullptr;
HashMap<String, TreeItem *> class_items;
TreeItem *matched_item = nullptr;
@@ -165,6 +171,12 @@ class EditorHelpSearch::Runner : public RefCounted {
void _populate_cache();
bool _find_or_create_item(TreeItem *p_parent, const String &p_item_meta, TreeItem *&r_item);
+ bool _fill();
+ bool _phase_fill_classes_init();
+ bool _phase_fill_classes();
+ bool _phase_fill_member_items_init();
+ bool _phase_fill_member_items();
+
bool _slice();
bool _phase_match_classes_init();
bool _phase_match_classes();
@@ -177,21 +189,25 @@ class EditorHelpSearch::Runner : public RefCounted {
String _build_method_tooltip(const DocData::ClassDoc *p_class_doc, const DocData::MethodDoc *p_doc) const;
String _build_keywords_tooltip(const String &p_keywords) const;
- void _match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, Vector<MemberMatch<DocData::MethodDoc>> *r_match_methods);
+ void _match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, LocalVector<MemberMatch<DocData::MethodDoc>> *r_match_methods);
bool _all_terms_in_name(const String &p_name) const;
String _match_keywords_in_all_terms(const String &p_keywords) const;
bool _match_string(const String &p_term, const String &p_string) const;
String _match_keywords(const String &p_term, const String &p_keywords) const;
void _match_item(TreeItem *p_item, const String &p_text, bool p_is_keywords = false);
TreeItem *_create_class_hierarchy(const ClassMatch &p_match);
+ TreeItem *_create_class_hierarchy(const DocData::ClassDoc *p_class_doc, const String &p_matching_keyword, bool p_gray);
TreeItem *_create_class_item(TreeItem *p_parent, const DocData::ClassDoc *p_doc, bool p_gray, const String &p_matching_keyword);
- TreeItem *_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const MemberMatch<DocData::MethodDoc> &p_match);
+ TreeItem *_create_category_item(TreeItem *p_parent, const String &p_class, const StringName &p_icon, const String &p_metatype, const String &p_type);
+ TreeItem *_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
+ TreeItem *_create_constructor_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
+ TreeItem *_create_operator_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
TreeItem *_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
TreeItem *_create_annotation_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match);
TreeItem *_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ConstantDoc> &p_match);
TreeItem *_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::PropertyDoc> &p_match);
TreeItem *_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ThemeItemDoc> &p_match);
- TreeItem *_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool p_is_deprecated, bool p_is_experimental, const String &p_matching_keyword);
+ TreeItem *_create_member_item(TreeItem *p_parent, const String &p_class_name, const StringName &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool p_is_deprecated, bool p_is_experimental, const String &p_matching_keyword);
public:
bool work(uint64_t slot = 100000);
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 199383c391..1e5acce032 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -4270,7 +4270,7 @@ void EditorInspector::_check_meta_name() {
if (meta_name.is_empty()) {
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name can't be empty."), EditorValidationPanel::MSG_ERROR);
- } else if (!meta_name.is_valid_identifier()) {
+ } else if (!meta_name.is_valid_ascii_identifier()) {
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name must be a valid identifier."), EditorValidationPanel::MSG_ERROR);
} else if (object->has_meta(meta_name)) {
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, vformat(TTR("Metadata with name \"%s\" already exists."), meta_name), EditorValidationPanel::MSG_ERROR);
diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp
index 46113ab2cb..e699b486ee 100644
--- a/editor/editor_interface.cpp
+++ b/editor/editor_interface.cpp
@@ -86,6 +86,10 @@ Ref<EditorSettings> EditorInterface::get_editor_settings() const {
return EditorSettings::get_singleton();
}
+EditorUndoRedoManager *EditorInterface::get_editor_undo_redo() const {
+ return EditorUndoRedoManager::get_singleton();
+}
+
TypedArray<Texture2D> EditorInterface::_make_mesh_previews(const TypedArray<Mesh> &p_meshes, int p_preview_size) {
Vector<Ref<Mesh>> meshes;
@@ -525,6 +529,7 @@ void EditorInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_resource_previewer"), &EditorInterface::get_resource_previewer);
ClassDB::bind_method(D_METHOD("get_selection"), &EditorInterface::get_selection);
ClassDB::bind_method(D_METHOD("get_editor_settings"), &EditorInterface::get_editor_settings);
+ ClassDB::bind_method(D_METHOD("get_editor_undo_redo"), &EditorInterface::get_editor_undo_redo);
ClassDB::bind_method(D_METHOD("make_mesh_previews", "meshes", "preview_size"), &EditorInterface::_make_mesh_previews);
diff --git a/editor/editor_interface.h b/editor/editor_interface.h
index 3ef4325780..2538f97c77 100644
--- a/editor/editor_interface.h
+++ b/editor/editor_interface.h
@@ -45,6 +45,7 @@ class EditorPlugin;
class EditorResourcePreview;
class EditorSelection;
class EditorSettings;
+class EditorUndoRedoManager;
class FileSystemDock;
class Mesh;
class Node;
@@ -93,6 +94,7 @@ public:
EditorResourcePreview *get_resource_previewer() const;
EditorSelection *get_selection() const;
Ref<EditorSettings> get_editor_settings() const;
+ EditorUndoRedoManager *get_editor_undo_redo() const;
Vector<Ref<Texture2D>> make_mesh_previews(const Vector<Ref<Mesh>> &p_meshes, Vector<Transform3D> *p_transforms, int p_preview_size);
diff --git a/editor/editor_locale_dialog.cpp b/editor/editor_locale_dialog.cpp
index 83f1c70c69..c36792c9e3 100644
--- a/editor/editor_locale_dialog.cpp
+++ b/editor/editor_locale_dialog.cpp
@@ -408,7 +408,7 @@ EditorLocaleDialog::EditorLocaleDialog() {
edit_filters->set_text(TTR("Edit Filters"));
edit_filters->set_toggle_mode(true);
edit_filters->set_pressed(false);
- edit_filters->connect("toggled", callable_mp(this, &EditorLocaleDialog::_edit_filters));
+ edit_filters->connect(SceneStringName(toggled), callable_mp(this, &EditorLocaleDialog::_edit_filters));
hb_filter->add_child(edit_filters);
}
{
@@ -416,7 +416,7 @@ EditorLocaleDialog::EditorLocaleDialog() {
advanced->set_text(TTR("Advanced"));
advanced->set_toggle_mode(true);
advanced->set_pressed(false);
- advanced->connect("toggled", callable_mp(this, &EditorLocaleDialog::_toggle_advanced));
+ advanced->connect(SceneStringName(toggled), callable_mp(this, &EditorLocaleDialog::_toggle_advanced));
hb_filter->add_child(advanced);
}
vb->add_child(hb_filter);
diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp
index baa44f56c4..aec374929e 100644
--- a/editor/editor_log.cpp
+++ b/editor/editor_log.cpp
@@ -52,10 +52,6 @@ void EditorLog::_error_handler(void *p_self, const char *p_func, const char *p_f
err_str = String::utf8(p_file) + ":" + itos(p_line) + " - " + String::utf8(p_error);
}
- if (p_editor_notify) {
- err_str += " (User)";
- }
-
MessageType message_type = p_type == ERR_HANDLER_WARNING ? MSG_TYPE_WARNING : MSG_TYPE_ERROR;
if (!Thread::is_main_thread()) {
@@ -514,7 +510,7 @@ EditorLog::EditorLog() {
collapse_button->set_tooltip_text(TTR("Collapse duplicate messages into one log entry. Shows number of occurrences."));
collapse_button->set_toggle_mode(true);
collapse_button->set_pressed(false);
- collapse_button->connect("toggled", callable_mp(this, &EditorLog::_set_collapse));
+ collapse_button->connect(SceneStringName(toggled), callable_mp(this, &EditorLog::_set_collapse));
hb_tools2->add_child(collapse_button);
// Show Search.
@@ -525,7 +521,7 @@ EditorLog::EditorLog() {
show_search_button->set_pressed(true);
show_search_button->set_shortcut(ED_SHORTCUT("editor/open_search", TTR("Focus Search/Filter Bar"), KeyModifierMask::CMD_OR_CTRL | Key::F));
show_search_button->set_shortcut_context(this);
- show_search_button->connect("toggled", callable_mp(this, &EditorLog::_set_search_visible));
+ show_search_button->connect(SceneStringName(toggled), callable_mp(this, &EditorLog::_set_search_visible));
hb_tools2->add_child(show_search_button);
// Message Type Filters.
diff --git a/editor/editor_log.h b/editor/editor_log.h
index 9c652e912a..899b4a9ac4 100644
--- a/editor/editor_log.h
+++ b/editor/editor_log.h
@@ -102,7 +102,7 @@ private:
toggle_button->add_theme_color_override("icon_color_pressed", Color(1, 1, 1, 1));
toggle_button->set_focus_mode(FOCUS_NONE);
// When toggled call the callback and pass the MessageType this button is for.
- toggle_button->connect("toggled", p_toggled_callback.bind(type));
+ toggle_button->connect(SceneStringName(toggled), p_toggled_callback.bind(type));
}
int get_message_count() {
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index d19d4740ee..d3fa2ed716 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -481,6 +481,9 @@ void EditorNode::_gdextensions_reloaded() {
// In case the developer is inspecting an object that will be changed by the reload.
InspectorDock::get_inspector_singleton()->update_tree();
+ // Reload script editor to revalidate GDScript if classes are added or removed.
+ ScriptEditor::get_singleton()->reload_scripts(true);
+
// Regenerate documentation.
EditorHelp::generate_doc();
}
@@ -740,6 +743,7 @@ void EditorNode::_notification(int p_what) {
RenderingServer::get_singleton()->viewport_set_disable_2d(get_scene_root()->get_viewport_rid(), true);
RenderingServer::get_singleton()->viewport_set_environment_mode(get_viewport()->get_viewport_rid(), RenderingServer::VIEWPORT_ENVIRONMENT_DISABLED);
+ DisplayServer::get_singleton()->screen_set_keep_on(EDITOR_GET("interface/editor/keep_screen_on"));
feature_profile_manager->notify_changed();
@@ -838,6 +842,7 @@ void EditorNode::_notification(int p_what) {
if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor")) {
_update_update_spinner();
_update_vsync_mode();
+ DisplayServer::get_singleton()->screen_set_keep_on(EDITOR_GET("interface/editor/keep_screen_on"));
}
#if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED)
@@ -2959,7 +2964,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
ERR_PRINT("Failed to load scene");
}
editor_data.move_edited_scene_to_index(cur_idx);
- EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_current_edited_scene_history_id());
+ EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_current_edited_scene_history_id(), false);
scene_tabs->set_current_tab(cur_idx);
} break;
@@ -3961,7 +3966,7 @@ void EditorNode::_set_current_scene_nocheck(int p_idx) {
editor_folding.load_scene_folding(editor_data.get_edited_scene_root(p_idx), editor_data.get_scene_path(p_idx));
}
- EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_scene_history_id(p_idx));
+ EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_scene_history_id(p_idx), false);
}
Dictionary state = editor_data.restore_edited_scene_state(editor_selection, &editor_history);
@@ -4071,7 +4076,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b
_set_current_scene(idx);
}
} else {
- EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_current_edited_scene_history_id());
+ EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_current_edited_scene_history_id(), false);
}
dependency_errors.clear();
@@ -4947,7 +4952,9 @@ bool EditorNode::is_object_of_custom_type(const Object *p_object, const StringNa
}
void EditorNode::progress_add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel) {
- if (singleton->cmdline_export_mode) {
+ if (!singleton) {
+ return;
+ } else if (singleton->cmdline_export_mode) {
print_line(p_task + ": begin: " + p_label + " steps: " + itos(p_steps));
} else if (singleton->progress_dialog) {
singleton->progress_dialog->add_task(p_task, p_label, p_steps, p_can_cancel);
@@ -4955,7 +4962,9 @@ void EditorNode::progress_add_task(const String &p_task, const String &p_label,
}
bool EditorNode::progress_task_step(const String &p_task, const String &p_state, int p_step, bool p_force_refresh) {
- if (singleton->cmdline_export_mode) {
+ if (!singleton) {
+ return false;
+ } else if (singleton->cmdline_export_mode) {
print_line("\t" + p_task + ": step " + itos(p_step) + ": " + p_state);
return false;
} else if (singleton->progress_dialog) {
@@ -4966,7 +4975,9 @@ bool EditorNode::progress_task_step(const String &p_task, const String &p_state,
}
void EditorNode::progress_end_task(const String &p_task) {
- if (singleton->cmdline_export_mode) {
+ if (!singleton) {
+ return;
+ } else if (singleton->cmdline_export_mode) {
print_line(p_task + ": end");
} else if (singleton->progress_dialog) {
singleton->progress_dialog->end_task(p_task);
@@ -5926,7 +5937,7 @@ void EditorNode::reload_scene(const String &p_path) {
bool is_unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(current_history_id);
// Scene is not open, so at it might be instantiated. We'll refresh the whole scene later.
- EditorUndoRedoManager::get_singleton()->clear_history(false, current_history_id);
+ EditorUndoRedoManager::get_singleton()->clear_history(current_history_id, false);
if (is_unsaved) {
EditorUndoRedoManager::get_singleton()->set_history_as_unsaved(current_history_id);
}
@@ -5945,7 +5956,7 @@ void EditorNode::reload_scene(const String &p_path) {
// Adjust index so tab is back a the previous position.
editor_data.move_edited_scene_to_index(scene_idx);
- EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_scene_history_id(scene_idx));
+ EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_scene_history_id(scene_idx), false);
// Recover the tab.
scene_tabs->set_current_tab(current_tab);
@@ -6090,7 +6101,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() {
bool is_unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(current_history_id);
// Clear the history for this affected tab.
- EditorUndoRedoManager::get_singleton()->clear_history(false, current_history_id);
+ EditorUndoRedoManager::get_singleton()->clear_history(current_history_id, false);
// Update the version
editor_data.is_scene_changed(current_scene_idx);
@@ -6886,10 +6897,10 @@ EditorNode::EditorNode() {
import_shader_file.instantiate();
ResourceFormatImporter::get_singleton()->add_importer(import_shader_file);
- Ref<ResourceImporterScene> import_scene = memnew(ResourceImporterScene(false, true));
+ Ref<ResourceImporterScene> import_scene = memnew(ResourceImporterScene("PackedScene", true));
ResourceFormatImporter::get_singleton()->add_importer(import_scene);
- Ref<ResourceImporterScene> import_animation = memnew(ResourceImporterScene(true, true));
+ Ref<ResourceImporterScene> import_animation = memnew(ResourceImporterScene("AnimationLibrary", true));
ResourceFormatImporter::get_singleton()->add_importer(import_animation);
{
@@ -7356,11 +7367,9 @@ EditorNode::EditorNode() {
settings_menu->set_item_tooltip(-1, TTR("Screenshots are stored in the user data folder (\"user://\")."));
-#ifndef ANDROID_ENABLED
ED_SHORTCUT_AND_COMMAND("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KeyModifierMask::SHIFT | Key::F11);
ED_SHORTCUT_OVERRIDE("editor/fullscreen_mode", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::F);
settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/fullscreen_mode"), SETTINGS_TOGGLE_FULLSCREEN);
-#endif
settings_menu->add_separator();
#ifndef ANDROID_ENABLED
@@ -7895,6 +7904,9 @@ EditorNode::EditorNode() {
ED_SHORTCUT_AND_COMMAND("editor/editor_next", TTR("Open the next Editor"));
ED_SHORTCUT_AND_COMMAND("editor/editor_prev", TTR("Open the previous Editor"));
+ // Apply setting presets in case the editor_settings file is missing values.
+ EditorSettingsDialog::update_navigation_preset();
+
screenshot_timer = memnew(Timer);
screenshot_timer->set_one_shot(true);
screenshot_timer->set_wait_time(settings_menu->get_submenu_popup_delay() + 0.1f);
diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp
index d58d0520cc..f5d016629f 100644
--- a/editor/editor_properties_array_dict.cpp
+++ b/editor/editor_properties_array_dict.cpp
@@ -644,6 +644,8 @@ void EditorPropertyArray::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_ENTER_TREE: {
change_type->clear();
+ change_type->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Remove Item"), Variant::VARIANT_MAX);
+ change_type->add_separator();
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
if (i == Variant::CALLABLE || i == Variant::SIGNAL || i == Variant::RID) {
// These types can't be constructed or serialized properly, so skip them.
@@ -653,8 +655,6 @@ void EditorPropertyArray::_notification(int p_what) {
String type = Variant::get_type_name(Variant::Type(i));
change_type->add_icon_item(get_editor_theme_icon(type), type, i);
}
- change_type->add_separator();
- change_type->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Remove Item"), Variant::VARIANT_MAX);
if (button_add_item) {
button_add_item->set_icon(get_editor_theme_icon(SNAME("Add")));
@@ -1117,6 +1117,8 @@ void EditorPropertyDictionary::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_ENTER_TREE: {
change_type->clear();
+ change_type->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Remove Item"), Variant::VARIANT_MAX);
+ change_type->add_separator();
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
if (i == Variant::CALLABLE || i == Variant::SIGNAL || i == Variant::RID) {
// These types can't be constructed or serialized properly, so skip them.
@@ -1126,8 +1128,6 @@ void EditorPropertyDictionary::_notification(int p_what) {
String type = Variant::get_type_name(Variant::Type(i));
change_type->add_icon_item(get_editor_theme_icon(type), type, i);
}
- change_type->add_separator();
- change_type->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Remove Item"), Variant::VARIANT_MAX);
if (button_add_item) {
button_add_item->set_icon(get_editor_theme_icon(SNAME("Add")));
diff --git a/editor/editor_properties_vector.cpp b/editor/editor_properties_vector.cpp
index 365cbc6ec8..9ff8bf674d 100644
--- a/editor/editor_properties_vector.cpp
+++ b/editor/editor_properties_vector.cpp
@@ -130,9 +130,11 @@ void EditorPropertyVectorN::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
if (linked->is_visible()) {
- const String key = vformat("%s:%s", get_edited_object()->get_class(), get_edited_property());
- linked->set_pressed_no_signal(EditorSettings::get_singleton()->get_project_metadata("linked_properties", key, true));
- _update_ratio();
+ if (get_edited_object()) {
+ const String key = vformat("%s:%s", get_edited_object()->get_class(), get_edited_property());
+ linked->set_pressed_no_signal(EditorSettings::get_singleton()->get_project_metadata("linked_properties", key, true));
+ _update_ratio();
+ }
}
} break;
@@ -236,7 +238,7 @@ EditorPropertyVectorN::EditorPropertyVectorN(Variant::Type p_type, bool p_force_
linked->set_stretch_mode(TextureButton::STRETCH_KEEP_CENTERED);
linked->set_tooltip_text(TTR("Lock/Unlock Component Ratio"));
linked->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyVectorN::_update_ratio));
- linked->connect(SNAME("toggled"), callable_mp(this, &EditorPropertyVectorN::_store_link));
+ linked->connect(SceneStringName(toggled), callable_mp(this, &EditorPropertyVectorN::_store_link));
hb->add_child(linked);
add_child(hb);
diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp
index 8935b9ad8a..f20dd992bb 100644
--- a/editor/editor_resource_picker.cpp
+++ b/editor/editor_resource_picker.cpp
@@ -175,6 +175,13 @@ void EditorResourcePicker::_file_quick_selected() {
_file_selected(quick_open->get_selected());
}
+void EditorResourcePicker::_resource_saved(Object *p_resource) {
+ if (edited_resource.is_valid() && p_resource == edited_resource.ptr()) {
+ emit_signal(SNAME("resource_changed"), edited_resource);
+ _update_resource();
+ }
+}
+
void EditorResourcePicker::_update_menu() {
_update_menu_items();
@@ -408,6 +415,10 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
if (edited_resource.is_null()) {
return;
}
+ Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved);
+ if (!EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) {
+ EditorNode::get_singleton()->connect("resource_saved", resource_saved);
+ }
EditorNode::get_singleton()->save_resource_as(edited_resource);
} break;
@@ -833,6 +844,13 @@ void EditorResourcePicker::_notification(int p_what) {
assign_button->queue_redraw();
}
} break;
+
+ case NOTIFICATION_EXIT_TREE: {
+ Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved);
+ if (EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) {
+ EditorNode::get_singleton()->disconnect("resource_saved", resource_saved);
+ }
+ } break;
}
}
diff --git a/editor/editor_resource_picker.h b/editor/editor_resource_picker.h
index 28229e6b37..05e392da2c 100644
--- a/editor/editor_resource_picker.h
+++ b/editor/editor_resource_picker.h
@@ -91,6 +91,8 @@ class EditorResourcePicker : public HBoxContainer {
void _file_quick_selected();
void _file_selected(const String &p_path);
+ void _resource_saved(Object *p_resource);
+
void _update_menu();
void _update_menu_items();
void _edit_menu_cbk(int p_which);
diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp
index 71865f7e8c..956fdc5cfa 100644
--- a/editor/editor_resource_preview.cpp
+++ b/editor/editor_resource_preview.cpp
@@ -221,7 +221,9 @@ void EditorResourcePreview::_generate_preview(Ref<ImageTexture> &r_texture, Ref<
r_small_texture->set_image(small_image);
}
- break;
+ if (generated.is_valid()) {
+ break;
+ }
}
if (!p_item.resource.is_valid()) {
diff --git a/editor/editor_run_native.cpp b/editor/editor_run_native.cpp
index 5d378820ae..e0e1ef6d19 100644
--- a/editor/editor_run_native.cpp
+++ b/editor/editor_run_native.cpp
@@ -141,7 +141,7 @@ Error EditorRunNative::start_run_native(int p_id) {
emit_signal(SNAME("native_run"), preset);
- int flags = 0;
+ BitField<EditorExportPlatform::DebugFlags> flags = 0;
bool deploy_debug_remote = is_deploy_debug_remote_enabled();
bool deploy_dumb = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_file_server", false);
@@ -149,16 +149,16 @@ Error EditorRunNative::start_run_native(int p_id) {
bool debug_navigation = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_navigation", false);
if (deploy_debug_remote) {
- flags |= EditorExportPlatform::DEBUG_FLAG_REMOTE_DEBUG;
+ flags.set_flag(EditorExportPlatform::DEBUG_FLAG_REMOTE_DEBUG);
}
if (deploy_dumb) {
- flags |= EditorExportPlatform::DEBUG_FLAG_DUMB_CLIENT;
+ flags.set_flag(EditorExportPlatform::DEBUG_FLAG_DUMB_CLIENT);
}
if (debug_collisions) {
- flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_COLLISIONS;
+ flags.set_flag(EditorExportPlatform::DEBUG_FLAG_VIEW_COLLISIONS);
}
if (debug_navigation) {
- flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION;
+ flags.set_flag(EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION);
}
eep->clear_messages();
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index a4cb3fbb68..b5c11b574e 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -467,6 +467,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/show_update_spinner", 0, "Auto (Disabled),Enabled,Disabled")
#endif
+ _initial_set("interface/editor/keep_screen_on", false);
EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_RANGE, "interface/editor/low_processor_mode_sleep_usec", 6900, "1,100000,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
// Default unfocused usec sleep is for 10 FPS. Allow an unfocused FPS limit
// as low as 1 FPS for those who really need low power usage (but don't need
@@ -730,14 +731,14 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
// 3D: Navigation
_initial_set("editors/3d/navigation/invert_x_axis", false);
_initial_set("editors/3d/navigation/invert_y_axis", false);
- EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/navigation_scheme", 0, "Godot,Maya,Modo")
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/navigation_scheme", 0, "Godot,Maya,Modo,Custom")
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/orbit_mouse_button", 1, "Left Mouse,Middle Mouse,Right Mouse")
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/pan_mouse_button", 1, "Left Mouse,Middle Mouse,Right Mouse")
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/zoom_mouse_button", 1, "Left Mouse,Middle Mouse,Right Mouse")
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/zoom_style", 0, "Vertical,Horizontal")
_initial_set("editors/3d/navigation/emulate_numpad", false);
_initial_set("editors/3d/navigation/emulate_3_button_mouse", false);
- EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/orbit_modifier", 0, "None,Shift,Alt,Meta,Ctrl")
- EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/pan_modifier", 1, "None,Shift,Alt,Meta,Ctrl")
- EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d/navigation/zoom_modifier", 4, "None,Shift,Alt,Meta,Ctrl")
_initial_set("editors/3d/navigation/warped_mouse_panning", true);
// 3D: Navigation feel
@@ -767,6 +768,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/2d/bone_outline_size", 2.0, "0.01,8,0.01,or_greater")
_initial_set("editors/2d/viewport_border_color", Color(0.4, 0.4, 1.0, 0.4));
_initial_set("editors/2d/use_integer_zoom_by_default", false);
+ EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/2d/zoom_speed_factor", 1.1, "1.01,2,0.01")
// Panning
// Enum should be in sync with ControlScheme in ViewPanner.
@@ -825,6 +827,12 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
String android_window_hints = "Auto (based on screen size):0,Same as Editor:1,Side-by-side with Editor:2";
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/android_window", 0, android_window_hints)
+ int default_play_window_pip_mode = 0;
+#ifdef ANDROID_ENABLED
+ default_play_window_pip_mode = 2;
+#endif
+ EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/play_window_pip_mode", default_play_window_pip_mode, "Disabled:0,Enabled:1,Enabled when Play window is same as Editor:2")
+
// Auto save
_initial_set("run/auto_save/save_before_running", true);
diff --git a/editor/editor_settings_dialog.cpp b/editor/editor_settings_dialog.cpp
index a71d43ad51..7e4dec86e9 100644
--- a/editor/editor_settings_dialog.cpp
+++ b/editor/editor_settings_dialog.cpp
@@ -42,6 +42,7 @@
#include "editor/editor_undo_redo_manager.h"
#include "editor/event_listener_line_edit.h"
#include "editor/input_event_configuration_dialog.h"
+#include "editor/plugins/node_3d_editor_plugin.h"
#include "editor/themes/editor_scale.h"
#include "editor/themes/editor_theme_manager.h"
#include "scene/gui/panel_container.h"
@@ -74,7 +75,82 @@ void EditorSettingsDialog::_settings_property_edited(const String &p_name) {
EditorSettings::get_singleton()->set_manually("text_editor/theme/color_theme", "Custom");
} else if (full_name.begins_with("editors/visual_editors/connection_colors") || full_name.begins_with("editors/visual_editors/category_colors")) {
EditorSettings::get_singleton()->set_manually("editors/visual_editors/color_theme", "Custom");
- }
+ } else if (full_name == "editors/3d/navigation/orbit_mouse_button" || full_name == "editors/3d/navigation/pan_mouse_button" || full_name == "editors/3d/navigation/zoom_mouse_button") {
+ EditorSettings::get_singleton()->set_manually("editors/3d/navigation/navigation_scheme", (int)Node3DEditorViewport::NAVIGATION_CUSTOM);
+ } else if (full_name == "editors/3d/navigation/navigation_scheme") {
+ update_navigation_preset();
+ }
+}
+
+void EditorSettingsDialog::update_navigation_preset() {
+ Node3DEditorViewport::NavigationScheme nav_scheme = (Node3DEditorViewport::NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
+ Node3DEditorViewport::ViewportNavMouseButton set_orbit_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
+ Node3DEditorViewport::ViewportNavMouseButton set_pan_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
+ Node3DEditorViewport::ViewportNavMouseButton set_zoom_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
+ Ref<InputEventKey> orbit_mod_key_1;
+ Ref<InputEventKey> orbit_mod_key_2;
+ Ref<InputEventKey> pan_mod_key_1;
+ Ref<InputEventKey> pan_mod_key_2;
+ Ref<InputEventKey> zoom_mod_key_1;
+ Ref<InputEventKey> zoom_mod_key_2;
+ bool set_preset = false;
+
+ if (nav_scheme == Node3DEditorViewport::NAVIGATION_GODOT) {
+ set_preset = true;
+ set_orbit_mouse_button = Node3DEditorViewport::NAVIGATION_MIDDLE_MOUSE;
+ set_pan_mouse_button = Node3DEditorViewport::NAVIGATION_MIDDLE_MOUSE;
+ set_zoom_mouse_button = Node3DEditorViewport::NAVIGATION_MIDDLE_MOUSE;
+ orbit_mod_key_1 = InputEventKey::create_reference(Key::NONE);
+ orbit_mod_key_2 = InputEventKey::create_reference(Key::NONE);
+ pan_mod_key_1 = InputEventKey::create_reference(Key::SHIFT);
+ pan_mod_key_2 = InputEventKey::create_reference(Key::NONE);
+ zoom_mod_key_1 = InputEventKey::create_reference(Key::SHIFT);
+ zoom_mod_key_2 = InputEventKey::create_reference(Key::CTRL);
+ } else if (nav_scheme == Node3DEditorViewport::NAVIGATION_MAYA) {
+ set_preset = true;
+ set_orbit_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
+ set_pan_mouse_button = Node3DEditorViewport::NAVIGATION_MIDDLE_MOUSE;
+ set_zoom_mouse_button = Node3DEditorViewport::NAVIGATION_RIGHT_MOUSE;
+ orbit_mod_key_1 = InputEventKey::create_reference(Key::ALT);
+ orbit_mod_key_2 = InputEventKey::create_reference(Key::NONE);
+ pan_mod_key_1 = InputEventKey::create_reference(Key::NONE);
+ pan_mod_key_2 = InputEventKey::create_reference(Key::NONE);
+ zoom_mod_key_1 = InputEventKey::create_reference(Key::ALT);
+ zoom_mod_key_2 = InputEventKey::create_reference(Key::NONE);
+ } else if (nav_scheme == Node3DEditorViewport::NAVIGATION_MODO) {
+ set_preset = true;
+ set_orbit_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
+ set_pan_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
+ set_zoom_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
+ orbit_mod_key_1 = InputEventKey::create_reference(Key::ALT);
+ orbit_mod_key_2 = InputEventKey::create_reference(Key::NONE);
+ pan_mod_key_1 = InputEventKey::create_reference(Key::SHIFT);
+ pan_mod_key_2 = InputEventKey::create_reference(Key::ALT);
+ zoom_mod_key_1 = InputEventKey::create_reference(Key::ALT);
+ zoom_mod_key_2 = InputEventKey::create_reference(Key::CTRL);
+ }
+ // Set settings to the desired preset values.
+ if (set_preset) {
+ EditorSettings::get_singleton()->set_manually("editors/3d/navigation/orbit_mouse_button", (int)set_orbit_mouse_button);
+ EditorSettings::get_singleton()->set_manually("editors/3d/navigation/pan_mouse_button", (int)set_pan_mouse_button);
+ EditorSettings::get_singleton()->set_manually("editors/3d/navigation/zoom_mouse_button", (int)set_zoom_mouse_button);
+ _set_shortcut_input("spatial_editor/viewport_orbit_modifier_1", orbit_mod_key_1);
+ _set_shortcut_input("spatial_editor/viewport_orbit_modifier_2", orbit_mod_key_2);
+ _set_shortcut_input("spatial_editor/viewport_pan_modifier_1", pan_mod_key_1);
+ _set_shortcut_input("spatial_editor/viewport_pan_modifier_2", pan_mod_key_2);
+ _set_shortcut_input("spatial_editor/viewport_zoom_modifier_1", zoom_mod_key_1);
+ _set_shortcut_input("spatial_editor/viewport_zoom_modifier_2", zoom_mod_key_2);
+ }
+}
+
+void EditorSettingsDialog::_set_shortcut_input(const String &p_name, Ref<InputEventKey> &p_event) {
+ Array sc_events;
+ if (p_event->get_keycode() != Key::NONE) {
+ sc_events.push_back((Variant)p_event);
+ }
+
+ Ref<Shortcut> sc = EditorSettings::get_singleton()->get_shortcut(p_name);
+ sc->set_events(sc_events);
}
void EditorSettingsDialog::_settings_save() {
@@ -97,6 +173,8 @@ void EditorSettingsDialog::popup_edit_settings() {
EditorSettings::get_singleton()->list_text_editor_themes(); // make sure we have an up to date list of themes
+ _update_dynamic_property_hints();
+
inspector->edit(EditorSettings::get_singleton());
inspector->get_inspector()->update_tree();
@@ -139,8 +217,8 @@ void EditorSettingsDialog::_notification(int p_what) {
case NOTIFICATION_READY: {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->get_or_create_history(EditorUndoRedoManager::GLOBAL_HISTORY).undo_redo->set_method_notify_callback(EditorDebuggerNode::_method_changeds, nullptr);
- undo_redo->get_or_create_history(EditorUndoRedoManager::GLOBAL_HISTORY).undo_redo->set_property_notify_callback(EditorDebuggerNode::_property_changeds, nullptr);
+ undo_redo->get_or_create_history(EditorUndoRedoManager::GLOBAL_HISTORY).undo_redo->set_method_notify_callback(EditorDebuggerNode::_methods_changed, nullptr);
+ undo_redo->get_or_create_history(EditorUndoRedoManager::GLOBAL_HISTORY).undo_redo->set_property_notify_callback(EditorDebuggerNode::_properties_changed, nullptr);
undo_redo->get_or_create_history(EditorUndoRedoManager::GLOBAL_HISTORY).undo_redo->set_commit_notify_callback(_undo_redo_callback, this);
} break;
@@ -160,6 +238,12 @@ void EditorSettingsDialog::_notification(int p_what) {
_update_shortcuts();
}
+ if (EditorSettings::get_singleton()->check_changed_settings_in_group("editors/3d/navigation")) {
+ // Shortcuts may have changed, so dynamic hint values must update.
+ _update_dynamic_property_hints();
+ inspector->get_inspector()->update_tree();
+ }
+
if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/localize_settings")) {
inspector->update_category_list();
}
@@ -256,6 +340,13 @@ void EditorSettingsDialog::_update_shortcut_events(const String &p_path, const A
undo_redo->add_do_method(this, "_settings_changed");
undo_redo->add_undo_method(this, "_settings_changed");
undo_redo->commit_action();
+
+ bool path_is_orbit_mod = p_path == "spatial_editor/viewport_orbit_modifier_1" || p_path == "spatial_editor/viewport_orbit_modifier_2";
+ bool path_is_pan_mod = p_path == "spatial_editor/viewport_pan_modifier_1" || p_path == "spatial_editor/viewport_pan_modifier_2";
+ bool path_is_zoom_mod = p_path == "spatial_editor/viewport_zoom_modifier_1" || p_path == "spatial_editor/viewport_zoom_modifier_2";
+ if (path_is_orbit_mod || path_is_pan_mod || path_is_zoom_mod) {
+ EditorSettings::get_singleton()->set_manually("editors/3d/navigation/navigation_scheme", (int)Node3DEditorViewport::NAVIGATION_CUSTOM);
+ }
}
Array EditorSettingsDialog::_event_list_to_array_helper(const List<Ref<InputEvent>> &p_events) {
@@ -661,6 +752,40 @@ void EditorSettingsDialog::drop_data_fw(const Point2 &p_point, const Variant &p_
void EditorSettingsDialog::_tabs_tab_changed(int p_tab) {
_focus_current_search_box();
+
+ // When tab has switched, shortcuts may have changed.
+ _update_dynamic_property_hints();
+ inspector->get_inspector()->update_tree();
+}
+
+void EditorSettingsDialog::_update_dynamic_property_hints() {
+ // Calling add_property_hint overrides the existing hint.
+ EditorSettings *settings = EditorSettings::get_singleton();
+ settings->add_property_hint(_create_mouse_shortcut_property_info("editors/3d/navigation/orbit_mouse_button", "spatial_editor/viewport_orbit_modifier_1", "spatial_editor/viewport_orbit_modifier_2"));
+ settings->add_property_hint(_create_mouse_shortcut_property_info("editors/3d/navigation/pan_mouse_button", "spatial_editor/viewport_pan_modifier_1", "spatial_editor/viewport_pan_modifier_2"));
+ settings->add_property_hint(_create_mouse_shortcut_property_info("editors/3d/navigation/zoom_mouse_button", "spatial_editor/viewport_zoom_modifier_1", "spatial_editor/viewport_zoom_modifier_2"));
+}
+
+PropertyInfo EditorSettingsDialog::_create_mouse_shortcut_property_info(const String &p_property_name, const String &p_shortcut_1_name, const String &p_shortcut_2_name) {
+ String hint_string;
+ hint_string += _get_shortcut_button_string(p_shortcut_1_name) + _get_shortcut_button_string(p_shortcut_2_name);
+ hint_string += "Left Mouse,";
+ hint_string += _get_shortcut_button_string(p_shortcut_1_name) + _get_shortcut_button_string(p_shortcut_2_name);
+ hint_string += "Middle Mouse,";
+ hint_string += _get_shortcut_button_string(p_shortcut_1_name) + _get_shortcut_button_string(p_shortcut_2_name);
+ hint_string += "Right Mouse";
+
+ return PropertyInfo(Variant::INT, p_property_name, PROPERTY_HINT_ENUM, hint_string);
+}
+
+String EditorSettingsDialog::_get_shortcut_button_string(const String &p_shortcut_name) {
+ String button_string;
+ Ref<Shortcut> shortcut_ref = EditorSettings::get_singleton()->get_shortcut(p_shortcut_name);
+ Array events = shortcut_ref->get_events();
+ for (Ref<InputEvent> input_event : events) {
+ button_string += input_event->as_text() + " + ";
+ }
+ return button_string;
}
void EditorSettingsDialog::_focus_current_search_box() {
diff --git a/editor/editor_settings_dialog.h b/editor/editor_settings_dialog.h
index cab8fe9da1..c213f737e2 100644
--- a/editor/editor_settings_dialog.h
+++ b/editor/editor_settings_dialog.h
@@ -100,6 +100,10 @@ class EditorSettingsDialog : public AcceptDialog {
void _tabs_tab_changed(int p_tab);
void _focus_current_search_box();
+ void _update_dynamic_property_hints();
+ PropertyInfo _create_mouse_shortcut_property_info(const String &p_property_name, const String &p_shortcut_1_name, const String &p_shortcut_2_name);
+ String _get_shortcut_button_string(const String &p_shortcut_name);
+
void _filter_shortcuts(const String &p_filter);
void _filter_shortcuts_by_event(const Ref<InputEvent> &p_event);
bool _should_display_shortcut(const String &p_name, const Array &p_events) const;
@@ -107,6 +111,7 @@ class EditorSettingsDialog : public AcceptDialog {
void _update_shortcuts();
void _shortcut_button_pressed(Object *p_item, int p_column, int p_idx, MouseButton p_button = MouseButton::LEFT);
void _shortcut_cell_double_clicked();
+ static void _set_shortcut_input(const String &p_name, Ref<InputEventKey> &p_event);
static void _undo_redo_callback(void *p_self, const String &p_name);
@@ -124,6 +129,7 @@ protected:
public:
void popup_edit_settings();
+ static void update_navigation_preset();
EditorSettingsDialog();
~EditorSettingsDialog();
diff --git a/editor/editor_undo_redo_manager.cpp b/editor/editor_undo_redo_manager.cpp
index 55bc198dfb..c0bf216634 100644
--- a/editor/editor_undo_redo_manager.cpp
+++ b/editor/editor_undo_redo_manager.cpp
@@ -390,7 +390,7 @@ bool EditorUndoRedoManager::has_history(int p_idx) const {
return history_map.has(p_idx);
}
-void EditorUndoRedoManager::clear_history(bool p_increase_version, int p_idx) {
+void EditorUndoRedoManager::clear_history(int p_idx, bool p_increase_version) {
if (p_idx != INVALID_HISTORY) {
History &history = get_or_create_history(p_idx);
history.undo_redo->clear_history(p_increase_version);
@@ -507,6 +507,7 @@ void EditorUndoRedoManager::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_object_history_id", "object"), &EditorUndoRedoManager::get_history_id_for_object);
ClassDB::bind_method(D_METHOD("get_history_undo_redo", "id"), &EditorUndoRedoManager::get_history_undo_redo);
+ ClassDB::bind_method(D_METHOD("clear_history", "id", "increase_version"), &EditorUndoRedoManager::clear_history, DEFVAL(INVALID_HISTORY), DEFVAL(true));
ADD_SIGNAL(MethodInfo("history_changed"));
ADD_SIGNAL(MethodInfo("version_changed"));
diff --git a/editor/editor_undo_redo_manager.h b/editor/editor_undo_redo_manager.h
index 219d5e0702..54475c3c6c 100644
--- a/editor/editor_undo_redo_manager.h
+++ b/editor/editor_undo_redo_manager.h
@@ -125,7 +125,7 @@ public:
bool undo_history(int p_id);
bool redo();
bool redo_history(int p_id);
- void clear_history(bool p_increase_version = true, int p_idx = INVALID_HISTORY);
+ void clear_history(int p_idx = INVALID_HISTORY, bool p_increase_version = true);
void set_history_as_saved(int p_idx);
void set_history_as_unsaved(int p_idx);
diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp
index 72ab186036..975a601ae1 100644
--- a/editor/export/editor_export.cpp
+++ b/editor/export/editor_export.cpp
@@ -124,7 +124,17 @@ void EditorExport::_bind_methods() {
void EditorExport::add_export_platform(const Ref<EditorExportPlatform> &p_platform) {
export_platforms.push_back(p_platform);
+
should_update_presets = true;
+ should_reload_presets = true;
+}
+
+void EditorExport::remove_export_platform(const Ref<EditorExportPlatform> &p_platform) {
+ export_platforms.erase(p_platform);
+ p_platform->cleanup();
+
+ should_update_presets = true;
+ should_reload_presets = true;
}
int EditorExport::get_export_platform_count() {
@@ -244,7 +254,7 @@ void EditorExport::load_config() {
if (!preset.is_valid()) {
index++;
- ERR_CONTINUE(!preset.is_valid());
+ continue; // Unknown platform, skip without error (platform might be loaded later).
}
preset->set_name(config->get_value(section, "name"));
@@ -343,6 +353,12 @@ void EditorExport::load_config() {
void EditorExport::update_export_presets() {
HashMap<StringName, List<EditorExportPlatform::ExportOption>> platform_options;
+ if (should_reload_presets) {
+ should_reload_presets = false;
+ export_presets.clear();
+ load_config();
+ }
+
for (int i = 0; i < export_platforms.size(); i++) {
Ref<EditorExportPlatform> platform = export_platforms[i];
diff --git a/editor/export/editor_export.h b/editor/export/editor_export.h
index f8cb90dc39..ebb2038f53 100644
--- a/editor/export/editor_export.h
+++ b/editor/export/editor_export.h
@@ -47,6 +47,7 @@ class EditorExport : public Node {
Timer *save_timer = nullptr;
bool block_save = false;
bool should_update_presets = false;
+ bool should_reload_presets = false;
static EditorExport *singleton;
@@ -66,6 +67,7 @@ public:
void add_export_platform(const Ref<EditorExportPlatform> &p_platform);
int get_export_platform_count();
Ref<EditorExportPlatform> get_export_platform(int p_idx);
+ void remove_export_platform(const Ref<EditorExportPlatform> &p_platform);
void add_export_preset(const Ref<EditorExportPreset> &p_preset, int p_at_pos = -1);
int get_export_preset_count() const;
diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp
index 8b31eda3bc..7ad589a58d 100644
--- a/editor/export/editor_export_platform.cpp
+++ b/editor/export/editor_export_platform.cpp
@@ -71,7 +71,7 @@ bool EditorExportPlatform::fill_log_messages(RichTextLabel *p_log, Error p_err)
p_log->add_text(" ");
p_log->add_text(get_name());
p_log->add_text(" - ");
- if (p_err == OK) {
+ if (p_err == OK && get_worst_message_type() < EditorExportPlatform::EXPORT_MESSAGE_ERROR) {
if (get_worst_message_type() >= EditorExportPlatform::EXPORT_MESSAGE_WARNING) {
p_log->add_image(p_log->get_editor_theme_icon(SNAME("StatusWarning")), 16 * EDSCALE, 16 * EDSCALE, Color(1.0, 1.0, 1.0), INLINE_ALIGNMENT_CENTER);
p_log->add_text(" ");
@@ -167,58 +167,6 @@ bool EditorExportPlatform::fill_log_messages(RichTextLabel *p_log, Error p_err)
return has_messages;
}
-void EditorExportPlatform::gen_debug_flags(Vector<String> &r_flags, int p_flags) {
- String host = EDITOR_GET("network/debug/remote_host");
- int remote_port = (int)EDITOR_GET("network/debug/remote_port");
-
- if (EditorSettings::get_singleton()->has_setting("export/android/use_wifi_for_remote_debug") && EDITOR_GET("export/android/use_wifi_for_remote_debug")) {
- host = EDITOR_GET("export/android/wifi_remote_debug_host");
- } else if (p_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST) {
- host = "localhost";
- }
-
- if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
- int port = EDITOR_GET("filesystem/file_server/port");
- String passwd = EDITOR_GET("filesystem/file_server/password");
- r_flags.push_back("--remote-fs");
- r_flags.push_back(host + ":" + itos(port));
- if (!passwd.is_empty()) {
- r_flags.push_back("--remote-fs-password");
- r_flags.push_back(passwd);
- }
- }
-
- if (p_flags & DEBUG_FLAG_REMOTE_DEBUG) {
- r_flags.push_back("--remote-debug");
-
- r_flags.push_back(get_debug_protocol() + host + ":" + String::num(remote_port));
-
- List<String> breakpoints;
- ScriptEditor::get_singleton()->get_breakpoints(&breakpoints);
-
- if (breakpoints.size()) {
- r_flags.push_back("--breakpoints");
- String bpoints;
- for (const List<String>::Element *E = breakpoints.front(); E; E = E->next()) {
- bpoints += E->get().replace(" ", "%20");
- if (E->next()) {
- bpoints += ",";
- }
- }
-
- r_flags.push_back(bpoints);
- }
- }
-
- if (p_flags & DEBUG_FLAG_VIEW_COLLISIONS) {
- r_flags.push_back("--debug-collisions");
- }
-
- if (p_flags & DEBUG_FLAG_VIEW_NAVIGATION) {
- r_flags.push_back("--debug-navigation");
- }
-}
-
Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
ERR_FAIL_COND_V_MSG(p_total < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export.");
@@ -530,7 +478,7 @@ HashSet<String> EditorExportPlatform::get_features(const Ref<EditorExportPreset>
return result;
}
-EditorExportPlatform::ExportNotifier::ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+EditorExportPlatform::ExportNotifier::ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
HashSet<String> features = p_platform.get_features(p_preset, p_debug);
Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
//initial export plugin callback
@@ -919,6 +867,55 @@ Vector<String> EditorExportPlatform::get_forced_export_files() {
return files;
}
+Error EditorExportPlatform::_script_save_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
+ Callable cb = ((ScriptCallbackData *)p_userdata)->file_cb;
+ ERR_FAIL_COND_V(!cb.is_valid(), FAILED);
+
+ Variant path = p_path;
+ Variant data = p_data;
+ Variant file = p_file;
+ Variant total = p_total;
+ Variant enc_in = p_enc_in_filters;
+ Variant enc_ex = p_enc_ex_filters;
+ Variant enc_key = p_key;
+
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[7] = { &path, &data, &file, &total, &enc_in, &enc_ex, &enc_key };
+
+ cb.callp(args, 7, ret, ce);
+ ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, vformat("Failed to execute file save callback: %s.", Variant::get_callable_error_text(cb, args, 7, ce)));
+
+ return (Error)ret.operator int();
+}
+
+Error EditorExportPlatform::_script_add_shared_object(void *p_userdata, const SharedObject &p_so) {
+ Callable cb = ((ScriptCallbackData *)p_userdata)->so_cb;
+ if (!cb.is_valid()) {
+ return OK; // Optional.
+ }
+
+ Variant path = p_so.path;
+ Variant tags = p_so.tags;
+ Variant target = p_so.target;
+
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[3] = { &path, &tags, &target };
+
+ cb.callp(args, 3, ret, ce);
+ ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, vformat("Failed to execute shared object save callback: %s.", Variant::get_callable_error_text(cb, args, 3, ce)));
+
+ return (Error)ret.operator int();
+}
+
+Error EditorExportPlatform::_export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, const Callable &p_save_func, const Callable &p_so_func) {
+ ScriptCallbackData data;
+ data.file_cb = p_save_func;
+ data.so_cb = p_so_func;
+ return export_project_files(p_preset, p_debug, _script_save_file, &data, _script_add_shared_object);
+}
+
Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func) {
//figure out paths of files that will be exported
HashSet<String> paths;
@@ -1425,7 +1422,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
return p_func(p_udata, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, key);
}
-Error EditorExportPlatform::_add_shared_object(void *p_userdata, const SharedObject &p_so) {
+Error EditorExportPlatform::_pack_add_shared_object(void *p_userdata, const SharedObject &p_so) {
PackData *pack_data = (PackData *)p_userdata;
if (pack_data->so_files) {
pack_data->so_files->push_back(p_so);
@@ -1434,6 +1431,15 @@ Error EditorExportPlatform::_add_shared_object(void *p_userdata, const SharedObj
return OK;
}
+Error EditorExportPlatform::_zip_add_shared_object(void *p_userdata, const SharedObject &p_so) {
+ ZipData *zip_data = (ZipData *)p_userdata;
+ if (zip_data->so_files) {
+ zip_data->so_files->push_back(p_so);
+ }
+
+ return OK;
+}
+
void EditorExportPlatform::zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) {
String dir = p_folder.is_empty() ? p_root_path : p_root_path.path_join(p_folder);
@@ -1551,6 +1557,54 @@ void EditorExportPlatform::zip_folder_recursive(zipFile &p_zip, const String &p_
da->list_dir_end();
}
+Dictionary EditorExportPlatform::_save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, bool p_embed) {
+ Vector<SharedObject> so_files;
+ int64_t embedded_start = 0;
+ int64_t embedded_size = 0;
+ Error err_code = save_pack(p_preset, p_debug, p_path, &so_files, p_embed, &embedded_start, &embedded_size);
+
+ Dictionary ret;
+ ret["result"] = err_code;
+ if (err_code == OK) {
+ Array arr;
+ for (const SharedObject &E : so_files) {
+ Dictionary so;
+ so["path"] = E.path;
+ so["tags"] = E.tags;
+ so["target_folder"] = E.target;
+ arr.push_back(so);
+ }
+ ret["so_files"] = arr;
+ if (p_embed) {
+ ret["embedded_start"] = embedded_start;
+ ret["embedded_size"] = embedded_size;
+ }
+ }
+
+ return ret;
+}
+
+Dictionary EditorExportPlatform::_save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) {
+ Vector<SharedObject> so_files;
+ Error err_code = save_zip(p_preset, p_debug, p_path, &so_files);
+
+ Dictionary ret;
+ ret["result"] = err_code;
+ if (err_code == OK) {
+ Array arr;
+ for (const SharedObject &E : so_files) {
+ Dictionary so;
+ so["path"] = E.path;
+ so["tags"] = E.tags;
+ so["target_folder"] = E.target;
+ arr.push_back(so);
+ }
+ ret["so_files"] = arr;
+ }
+
+ return ret;
+}
+
Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) {
EditorProgress ep("savepack", TTR("Packing"), 102, true);
@@ -1570,7 +1624,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
pd.f = ftmp;
pd.so_files = p_so_files;
- Error err = export_project_files(p_preset, p_debug, _save_pack_file, &pd, _add_shared_object);
+ Error err = export_project_files(p_preset, p_debug, _save_pack_file, &pd, _pack_add_shared_object);
// Close temp file.
pd.f.unref();
@@ -1777,7 +1831,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
return OK;
}
-Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) {
+Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files) {
EditorProgress ep("savezip", TTR("Packing"), 102, true);
Ref<FileAccess> io_fa;
@@ -1787,8 +1841,9 @@ Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bo
ZipData zd;
zd.ep = &ep;
zd.zip = zip;
+ zd.so_files = p_so_files;
- Error err = export_project_files(p_preset, p_debug, _save_zip_file, &zd);
+ Error err = export_project_files(p_preset, p_debug, _save_zip_file, &zd, _zip_add_shared_object);
if (err != OK && err != ERR_SKIP) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Save ZIP"), TTR("Failed to export project files."));
}
@@ -1798,45 +1853,48 @@ Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bo
return OK;
}
-Error EditorExportPlatform::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatform::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
return save_pack(p_preset, p_debug, p_path);
}
-Error EditorExportPlatform::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatform::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
return save_zip(p_preset, p_debug, p_path);
}
-void EditorExportPlatform::gen_export_flags(Vector<String> &r_flags, int p_flags) {
+Vector<String> EditorExportPlatform::gen_export_flags(BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ Vector<String> ret;
String host = EDITOR_GET("network/debug/remote_host");
int remote_port = (int)EDITOR_GET("network/debug/remote_port");
- if (p_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST) {
+ if (get_name() == "Android" && EditorSettings::get_singleton()->has_setting("export/android/use_wifi_for_remote_debug") && EDITOR_GET("export/android/use_wifi_for_remote_debug")) {
+ host = EDITOR_GET("export/android/wifi_remote_debug_host");
+ } else if (p_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST)) {
host = "localhost";
}
- if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) {
int port = EDITOR_GET("filesystem/file_server/port");
String passwd = EDITOR_GET("filesystem/file_server/password");
- r_flags.push_back("--remote-fs");
- r_flags.push_back(host + ":" + itos(port));
+ ret.push_back("--remote-fs");
+ ret.push_back(host + ":" + itos(port));
if (!passwd.is_empty()) {
- r_flags.push_back("--remote-fs-password");
- r_flags.push_back(passwd);
+ ret.push_back("--remote-fs-password");
+ ret.push_back(passwd);
}
}
- if (p_flags & DEBUG_FLAG_REMOTE_DEBUG) {
- r_flags.push_back("--remote-debug");
+ if (p_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) {
+ ret.push_back("--remote-debug");
- r_flags.push_back(get_debug_protocol() + host + ":" + String::num(remote_port));
+ ret.push_back(get_debug_protocol() + host + ":" + String::num(remote_port));
List<String> breakpoints;
ScriptEditor::get_singleton()->get_breakpoints(&breakpoints);
if (breakpoints.size()) {
- r_flags.push_back("--breakpoints");
+ ret.push_back("--breakpoints");
String bpoints;
for (List<String>::Element *E = breakpoints.front(); E; E = E->next()) {
bpoints += E->get().replace(" ", "%20");
@@ -1845,17 +1903,18 @@ void EditorExportPlatform::gen_export_flags(Vector<String> &r_flags, int p_flags
}
}
- r_flags.push_back(bpoints);
+ ret.push_back(bpoints);
}
}
- if (p_flags & DEBUG_FLAG_VIEW_COLLISIONS) {
- r_flags.push_back("--debug-collisions");
+ if (p_flags.has_flag(DEBUG_FLAG_VIEW_COLLISIONS)) {
+ ret.push_back("--debug-collisions");
}
- if (p_flags & DEBUG_FLAG_VIEW_NAVIGATION) {
- r_flags.push_back("--debug-navigation");
+ if (p_flags.has_flag(DEBUG_FLAG_VIEW_NAVIGATION)) {
+ ret.push_back("--debug-navigation");
}
+ return ret;
}
bool EditorExportPlatform::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
@@ -2035,8 +2094,61 @@ Error EditorExportPlatform::ssh_push_to_remote(const String &p_host, const Strin
return OK;
}
+Array EditorExportPlatform::get_current_presets() const {
+ Array ret;
+ for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
+ Ref<EditorExportPreset> ep = EditorExport::get_singleton()->get_export_preset(i);
+ if (ep->get_platform() == this) {
+ ret.push_back(ep);
+ }
+ }
+ return ret;
+}
+
void EditorExportPlatform::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_os_name"), &EditorExportPlatform::get_os_name);
+
+ ClassDB::bind_method(D_METHOD("create_preset"), &EditorExportPlatform::create_preset);
+
+ ClassDB::bind_method(D_METHOD("find_export_template", "template_file_name"), &EditorExportPlatform::_find_export_template);
+ ClassDB::bind_method(D_METHOD("get_current_presets"), &EditorExportPlatform::get_current_presets);
+
+ ClassDB::bind_method(D_METHOD("save_pack", "preset", "debug", "path", "embed"), &EditorExportPlatform::_save_pack, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("save_zip", "preset", "debug", "path"), &EditorExportPlatform::_save_zip);
+
+ ClassDB::bind_method(D_METHOD("gen_export_flags", "flags"), &EditorExportPlatform::gen_export_flags);
+
+ ClassDB::bind_method(D_METHOD("export_project_files", "preset", "debug", "save_cb", "shared_cb"), &EditorExportPlatform::_export_project_files, DEFVAL(Callable()));
+
+ ClassDB::bind_method(D_METHOD("export_project", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_project, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("export_pack", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_pack, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("export_zip", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_zip, DEFVAL(0));
+
+ ClassDB::bind_method(D_METHOD("clear_messages"), &EditorExportPlatform::clear_messages);
+ ClassDB::bind_method(D_METHOD("add_message", "type", "category", "message"), &EditorExportPlatform::add_message);
+ ClassDB::bind_method(D_METHOD("get_message_count"), &EditorExportPlatform::get_message_count);
+
+ ClassDB::bind_method(D_METHOD("get_message_type", "index"), &EditorExportPlatform::_get_message_type);
+ ClassDB::bind_method(D_METHOD("get_message_category", "index"), &EditorExportPlatform::_get_message_category);
+ ClassDB::bind_method(D_METHOD("get_message_text", "index"), &EditorExportPlatform::_get_message_text);
+ ClassDB::bind_method(D_METHOD("get_worst_message_type"), &EditorExportPlatform::get_worst_message_type);
+
+ ClassDB::bind_method(D_METHOD("ssh_run_on_remote", "host", "port", "ssh_arg", "cmd_args", "output", "port_fwd"), &EditorExportPlatform::_ssh_run_on_remote, DEFVAL(Array()), DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("ssh_run_on_remote_no_wait", "host", "port", "ssh_args", "cmd_args", "port_fwd"), &EditorExportPlatform::_ssh_run_on_remote_no_wait, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("ssh_push_to_remote", "host", "port", "scp_args", "src_file", "dst_file"), &EditorExportPlatform::ssh_push_to_remote);
+
+ ClassDB::bind_static_method("EditorExportPlatform", D_METHOD("get_forced_export_files"), &EditorExportPlatform::get_forced_export_files);
+
+ BIND_ENUM_CONSTANT(EXPORT_MESSAGE_NONE);
+ BIND_ENUM_CONSTANT(EXPORT_MESSAGE_INFO);
+ BIND_ENUM_CONSTANT(EXPORT_MESSAGE_WARNING);
+ BIND_ENUM_CONSTANT(EXPORT_MESSAGE_ERROR);
+
+ BIND_BITFIELD_FLAG(DEBUG_FLAG_DUMB_CLIENT);
+ BIND_BITFIELD_FLAG(DEBUG_FLAG_REMOTE_DEBUG);
+ BIND_BITFIELD_FLAG(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST);
+ BIND_BITFIELD_FLAG(DEBUG_FLAG_VIEW_COLLISIONS);
+ BIND_BITFIELD_FLAG(DEBUG_FLAG_VIEW_NAVIGATION);
}
EditorExportPlatform::EditorExportPlatform() {
diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h
index 3fd75ff67f..a800bb95e6 100644
--- a/editor/export/editor_export_platform.h
+++ b/editor/export/editor_export_platform.h
@@ -56,6 +56,14 @@ public:
typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
typedef Error (*EditorExportSaveSharedObject)(void *p_userdata, const SharedObject &p_so);
+ enum DebugFlags {
+ DEBUG_FLAG_DUMB_CLIENT = 1,
+ DEBUG_FLAG_REMOTE_DEBUG = 2,
+ DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST = 4,
+ DEBUG_FLAG_VIEW_COLLISIONS = 8,
+ DEBUG_FLAG_VIEW_NAVIGATION = 16,
+ };
+
enum ExportMessageType {
EXPORT_MESSAGE_NONE,
EXPORT_MESSAGE_INFO,
@@ -92,6 +100,7 @@ private:
struct ZipData {
void *zip = nullptr;
EditorProgress *ep = nullptr;
+ Vector<SharedObject> *so_files = nullptr;
};
Vector<ExportMessage> messages;
@@ -101,13 +110,22 @@ private:
void _export_find_dependencies(const String &p_path, HashSet<String> &p_paths);
static Error _save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
+ static Error _pack_add_shared_object(void *p_userdata, const SharedObject &p_so);
+
static Error _save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
+ static Error _zip_add_shared_object(void *p_userdata, const SharedObject &p_so);
+
+ struct ScriptCallbackData {
+ Callable file_cb;
+ Callable so_cb;
+ };
+
+ static Error _script_save_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
+ static Error _script_add_shared_object(void *p_userdata, const SharedObject &p_so);
void _edit_files_with_filter(Ref<DirAccess> &da, const Vector<String> &p_filters, HashSet<String> &r_list, bool exclude);
void _edit_filter_list(HashSet<String> &r_list, const String &p_filter, bool exclude);
- static Error _add_shared_object(void *p_userdata, const SharedObject &p_so);
-
struct FileExportCache {
uint64_t source_modified_time = 0;
String source_md5;
@@ -126,19 +144,46 @@ private:
protected:
struct ExportNotifier {
- ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags);
+ ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags);
~ExportNotifier();
};
HashSet<String> get_features(const Ref<EditorExportPreset> &p_preset, bool p_debug) const;
- bool exists_export_template(const String &template_file_name, String *err) const;
- String find_export_template(const String &template_file_name, String *err = nullptr) const;
- void gen_export_flags(Vector<String> &r_flags, int p_flags);
- void gen_debug_flags(Vector<String> &r_flags, int p_flags);
+ Dictionary _find_export_template(const String &p_template_file_name) const {
+ Dictionary ret;
+ String err;
+
+ String path = find_export_template(p_template_file_name, &err);
+ ret["result"] = (err.is_empty() && !path.is_empty()) ? OK : FAILED;
+ ret["path"] = path;
+ ret["error_string"] = err;
+
+ return ret;
+ }
+
+ bool exists_export_template(const String &p_template_file_name, String *r_err) const;
+ String find_export_template(const String &p_template_file_name, String *r_err = nullptr) const;
+ Vector<String> gen_export_flags(BitField<EditorExportPlatform::DebugFlags> p_flags);
virtual void zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name);
+ Error _ssh_run_on_remote(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, Array r_output = Array(), int p_port_fwd = -1) const {
+ String pipe;
+ Error err = ssh_run_on_remote(p_host, p_port, p_ssh_args, p_cmd_args, &pipe, p_port_fwd);
+ r_output.push_back(pipe);
+ return err;
+ }
+ OS::ProcessID _ssh_run_on_remote_no_wait(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, int p_port_fwd = -1) const {
+ OS::ProcessID pid = 0;
+ Error err = ssh_run_on_remote_no_wait(p_host, p_port, p_ssh_args, p_cmd_args, &pid, p_port_fwd);
+ if (err != OK) {
+ return -1;
+ } else {
+ return pid;
+ }
+ }
+
Error ssh_run_on_remote(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, String *r_out = nullptr, int p_port_fwd = -1) const;
Error ssh_run_on_remote_no_wait(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, OS::ProcessID *r_pid = nullptr, int p_port_fwd = -1) const;
Error ssh_push_to_remote(const String &p_host, const String &p_port, const Vector<String> &p_scp_args, const String &p_src_file, const String &p_dst_file) const;
@@ -195,6 +240,21 @@ public:
return messages[p_index];
}
+ virtual ExportMessageType _get_message_type(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, messages.size(), EXPORT_MESSAGE_NONE);
+ return messages[p_index].msg_type;
+ }
+
+ virtual String _get_message_category(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, messages.size(), String());
+ return messages[p_index].category;
+ }
+
+ virtual String _get_message_text(int p_index) const {
+ ERR_FAIL_INDEX_V(p_index, messages.size(), String());
+ return messages[p_index].text;
+ }
+
virtual ExportMessageType get_worst_message_type() const {
ExportMessageType worst_type = EXPORT_MESSAGE_NONE;
for (int i = 0; i < messages.size(); i++) {
@@ -216,10 +276,16 @@ public:
virtual String get_name() const = 0;
virtual Ref<Texture2D> get_logo() const = 0;
+ Array get_current_presets() const;
+
+ Error _export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, const Callable &p_save_func, const Callable &p_so_func);
Error export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func = nullptr);
+ Dictionary _save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, bool p_embed = false);
+ Dictionary _save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
+
Error save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, bool p_embed = false, int64_t *r_embedded_start = nullptr, int64_t *r_embedded_size = nullptr);
- Error save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
+ Error save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr);
virtual bool poll_export() { return false; }
virtual int get_options_count() const { return 0; }
@@ -229,31 +295,26 @@ public:
virtual String get_option_tooltip(int p_device) const { return ""; }
virtual String get_device_architecture(int p_device) const { return ""; }
- enum DebugFlags {
- DEBUG_FLAG_DUMB_CLIENT = 1,
- DEBUG_FLAG_REMOTE_DEBUG = 2,
- DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST = 4,
- DEBUG_FLAG_VIEW_COLLISIONS = 8,
- DEBUG_FLAG_VIEW_NAVIGATION = 16,
- };
-
virtual void cleanup() {}
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { return OK; }
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { return OK; }
virtual Ref<Texture2D> get_run_icon() const { return get_logo(); }
- bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const;
+ virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const;
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const = 0;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const = 0;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const = 0;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) = 0;
- virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0);
- virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0);
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) = 0;
+ virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0);
+ virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0);
virtual void get_platform_features(List<String> *r_features) const = 0;
- virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) = 0;
+ virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features){};
virtual String get_debug_protocol() const { return "tcp://"; }
EditorExportPlatform();
};
+VARIANT_ENUM_CAST(EditorExportPlatform::ExportMessageType)
+VARIANT_BITFIELD_CAST(EditorExportPlatform::DebugFlags);
+
#endif // EDITOR_EXPORT_PLATFORM_H
diff --git a/editor/export/editor_export_platform_extension.cpp b/editor/export/editor_export_platform_extension.cpp
new file mode 100644
index 0000000000..808a2076e2
--- /dev/null
+++ b/editor/export/editor_export_platform_extension.cpp
@@ -0,0 +1,317 @@
+/**************************************************************************/
+/* editor_export_platform_extension.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_export_platform_extension.h"
+
+void EditorExportPlatformExtension::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_config_error", "error_text"), &EditorExportPlatformExtension::set_config_error);
+ ClassDB::bind_method(D_METHOD("get_config_error"), &EditorExportPlatformExtension::get_config_error);
+
+ ClassDB::bind_method(D_METHOD("set_config_missing_templates", "missing_templates"), &EditorExportPlatformExtension::set_config_missing_templates);
+ ClassDB::bind_method(D_METHOD("get_config_missing_templates"), &EditorExportPlatformExtension::get_config_missing_templates);
+
+ GDVIRTUAL_BIND(_get_preset_features, "preset");
+ GDVIRTUAL_BIND(_is_executable, "path");
+ GDVIRTUAL_BIND(_get_export_options);
+ GDVIRTUAL_BIND(_should_update_export_options);
+ GDVIRTUAL_BIND(_get_export_option_visibility, "preset", "option");
+ GDVIRTUAL_BIND(_get_export_option_warning, "preset", "option");
+
+ GDVIRTUAL_BIND(_get_os_name);
+ GDVIRTUAL_BIND(_get_name);
+ GDVIRTUAL_BIND(_get_logo);
+
+ GDVIRTUAL_BIND(_poll_export);
+ GDVIRTUAL_BIND(_get_options_count);
+ GDVIRTUAL_BIND(_get_options_tooltip);
+
+ GDVIRTUAL_BIND(_get_option_icon, "device");
+ GDVIRTUAL_BIND(_get_option_label, "device");
+ GDVIRTUAL_BIND(_get_option_tooltip, "device");
+ GDVIRTUAL_BIND(_get_device_architecture, "device");
+
+ GDVIRTUAL_BIND(_cleanup);
+
+ GDVIRTUAL_BIND(_run, "preset", "device", "debug_flags");
+ GDVIRTUAL_BIND(_get_run_icon);
+
+ GDVIRTUAL_BIND(_can_export, "preset", "debug");
+ GDVIRTUAL_BIND(_has_valid_export_configuration, "preset", "debug");
+ GDVIRTUAL_BIND(_has_valid_project_configuration, "preset");
+
+ GDVIRTUAL_BIND(_get_binary_extensions, "preset");
+
+ GDVIRTUAL_BIND(_export_project, "preset", "debug", "path", "flags");
+ GDVIRTUAL_BIND(_export_pack, "preset", "debug", "path", "flags");
+ GDVIRTUAL_BIND(_export_zip, "preset", "debug", "path", "flags");
+
+ GDVIRTUAL_BIND(_get_platform_features);
+
+ GDVIRTUAL_BIND(_get_debug_protocol);
+}
+
+void EditorExportPlatformExtension::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const {
+ Vector<String> ret;
+ if (GDVIRTUAL_REQUIRED_CALL(_get_preset_features, p_preset, ret) && r_features) {
+ for (const String &E : ret) {
+ r_features->push_back(E);
+ }
+ }
+}
+
+bool EditorExportPlatformExtension::is_executable(const String &p_path) const {
+ bool ret = false;
+ GDVIRTUAL_CALL(_is_executable, p_path, ret);
+ return ret;
+}
+
+void EditorExportPlatformExtension::get_export_options(List<ExportOption> *r_options) const {
+ TypedArray<Dictionary> ret;
+ if (GDVIRTUAL_CALL(_get_export_options, ret) && r_options) {
+ for (const Variant &var : ret) {
+ const Dictionary &d = var;
+ ERR_CONTINUE(!d.has("name"));
+ ERR_CONTINUE(!d.has("type"));
+
+ PropertyInfo pinfo = PropertyInfo::from_dict(d);
+ ERR_CONTINUE(pinfo.name.is_empty() && (pinfo.usage & PROPERTY_USAGE_STORAGE));
+ ERR_CONTINUE(pinfo.type < 0 || pinfo.type >= Variant::VARIANT_MAX);
+
+ Variant default_value;
+ if (d.has("default_value")) {
+ default_value = d["default_value"];
+ }
+ bool update_visibility = false;
+ if (d.has("update_visibility")) {
+ update_visibility = d["update_visibility"];
+ }
+ bool required = false;
+ if (d.has("required")) {
+ required = d["required"];
+ }
+
+ r_options->push_back(ExportOption(pinfo, default_value, update_visibility, required));
+ }
+ }
+}
+
+bool EditorExportPlatformExtension::should_update_export_options() {
+ bool ret = false;
+ GDVIRTUAL_CALL(_should_update_export_options, ret);
+ return ret;
+}
+
+bool EditorExportPlatformExtension::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const {
+ bool ret = true;
+ GDVIRTUAL_CALL(_get_export_option_visibility, Ref<EditorExportPreset>(p_preset), p_option, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const {
+ String ret;
+ GDVIRTUAL_CALL(_get_export_option_warning, Ref<EditorExportPreset>(p_preset), p_name, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_os_name() const {
+ String ret;
+ GDVIRTUAL_REQUIRED_CALL(_get_os_name, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_name() const {
+ String ret;
+ GDVIRTUAL_REQUIRED_CALL(_get_name, ret);
+ return ret;
+}
+
+Ref<Texture2D> EditorExportPlatformExtension::get_logo() const {
+ Ref<Texture2D> ret;
+ GDVIRTUAL_REQUIRED_CALL(_get_logo, ret);
+ return ret;
+}
+
+bool EditorExportPlatformExtension::poll_export() {
+ bool ret = false;
+ GDVIRTUAL_CALL(_poll_export, ret);
+ return ret;
+}
+
+int EditorExportPlatformExtension::get_options_count() const {
+ int ret = 0;
+ GDVIRTUAL_CALL(_get_options_count, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_options_tooltip() const {
+ String ret;
+ GDVIRTUAL_CALL(_get_options_tooltip, ret);
+ return ret;
+}
+
+Ref<ImageTexture> EditorExportPlatformExtension::get_option_icon(int p_index) const {
+ Ref<ImageTexture> ret;
+ if (GDVIRTUAL_CALL(_get_option_icon, p_index, ret)) {
+ return ret;
+ }
+ return EditorExportPlatform::get_option_icon(p_index);
+}
+
+String EditorExportPlatformExtension::get_option_label(int p_device) const {
+ String ret;
+ GDVIRTUAL_CALL(_get_option_label, p_device, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_option_tooltip(int p_device) const {
+ String ret;
+ GDVIRTUAL_CALL(_get_option_tooltip, p_device, ret);
+ return ret;
+}
+
+String EditorExportPlatformExtension::get_device_architecture(int p_device) const {
+ String ret;
+ GDVIRTUAL_CALL(_get_device_architecture, p_device, ret);
+ return ret;
+}
+
+void EditorExportPlatformExtension::cleanup() {
+ GDVIRTUAL_CALL(_cleanup);
+}
+
+Error EditorExportPlatformExtension::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
+ Error ret = OK;
+ GDVIRTUAL_CALL(_run, p_preset, p_device, p_debug_flags, ret);
+ return ret;
+}
+
+Ref<Texture2D> EditorExportPlatformExtension::get_run_icon() const {
+ Ref<Texture2D> ret;
+ if (GDVIRTUAL_CALL(_get_run_icon, ret)) {
+ return ret;
+ }
+ return EditorExportPlatform::get_run_icon();
+}
+
+bool EditorExportPlatformExtension::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
+ bool ret = false;
+ config_error = r_error;
+ config_missing_templates = r_missing_templates;
+ if (GDVIRTUAL_CALL(_can_export, p_preset, p_debug, ret)) {
+ r_error = config_error;
+ r_missing_templates = config_missing_templates;
+ return ret;
+ }
+ return EditorExportPlatform::can_export(p_preset, r_error, r_missing_templates, p_debug);
+}
+
+bool EditorExportPlatformExtension::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const {
+ bool ret = false;
+ config_error = r_error;
+ config_missing_templates = r_missing_templates;
+ if (GDVIRTUAL_REQUIRED_CALL(_has_valid_export_configuration, p_preset, p_debug, ret)) {
+ r_error = config_error;
+ r_missing_templates = config_missing_templates;
+ }
+ return ret;
+}
+
+bool EditorExportPlatformExtension::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
+ bool ret = false;
+ config_error = r_error;
+ if (GDVIRTUAL_REQUIRED_CALL(_has_valid_project_configuration, p_preset, ret)) {
+ r_error = config_error;
+ }
+ return ret;
+}
+
+List<String> EditorExportPlatformExtension::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const {
+ List<String> ret_list;
+ Vector<String> ret;
+ if (GDVIRTUAL_REQUIRED_CALL(_get_binary_extensions, p_preset, ret)) {
+ for (const String &E : ret) {
+ ret_list.push_back(E);
+ }
+ }
+ return ret_list;
+}
+
+Error EditorExportPlatformExtension::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
+ Error ret = FAILED;
+ GDVIRTUAL_REQUIRED_CALL(_export_project, p_preset, p_debug, p_path, p_flags, ret);
+ return ret;
+}
+
+Error EditorExportPlatformExtension::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
+ Error ret = FAILED;
+ if (GDVIRTUAL_CALL(_export_pack, p_preset, p_debug, p_path, p_flags, ret)) {
+ return ret;
+ }
+ return save_pack(p_preset, p_debug, p_path);
+}
+
+Error EditorExportPlatformExtension::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
+ Error ret = FAILED;
+ if (GDVIRTUAL_CALL(_export_zip, p_preset, p_debug, p_path, p_flags, ret)) {
+ return ret;
+ }
+ return save_zip(p_preset, p_debug, p_path);
+}
+
+void EditorExportPlatformExtension::get_platform_features(List<String> *r_features) const {
+ Vector<String> ret;
+ if (GDVIRTUAL_REQUIRED_CALL(_get_platform_features, ret) && r_features) {
+ for (const String &E : ret) {
+ r_features->push_back(E);
+ }
+ }
+}
+
+String EditorExportPlatformExtension::get_debug_protocol() const {
+ String ret;
+ if (GDVIRTUAL_CALL(_get_debug_protocol, ret)) {
+ return ret;
+ }
+ return EditorExportPlatform::get_debug_protocol();
+}
+
+EditorExportPlatformExtension::EditorExportPlatformExtension() {
+ //NOP
+}
+
+EditorExportPlatformExtension::~EditorExportPlatformExtension() {
+ //NOP
+}
diff --git a/editor/export/editor_export_platform_extension.h b/editor/export/editor_export_platform_extension.h
new file mode 100644
index 0000000000..6391e65ac1
--- /dev/null
+++ b/editor/export/editor_export_platform_extension.h
@@ -0,0 +1,149 @@
+/**************************************************************************/
+/* editor_export_platform_extension.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 EDITOR_EXPORT_PLATFORM_EXTENSION_H
+#define EDITOR_EXPORT_PLATFORM_EXTENSION_H
+
+#include "editor_export_platform.h"
+#include "editor_export_preset.h"
+
+class EditorExportPlatformExtension : public EditorExportPlatform {
+ GDCLASS(EditorExportPlatformExtension, EditorExportPlatform);
+
+ mutable String config_error;
+ mutable bool config_missing_templates = false;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override;
+ GDVIRTUAL1RC(Vector<String>, _get_preset_features, Ref<EditorExportPreset>);
+
+ virtual bool is_executable(const String &p_path) const override;
+ GDVIRTUAL1RC(bool, _is_executable, const String &);
+
+ virtual void get_export_options(List<ExportOption> *r_options) const override;
+ GDVIRTUAL0RC(TypedArray<Dictionary>, _get_export_options);
+
+ virtual bool should_update_export_options() override;
+ GDVIRTUAL0R(bool, _should_update_export_options);
+
+ virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override;
+ GDVIRTUAL2RC(bool, _get_export_option_visibility, Ref<EditorExportPreset>, const String &);
+
+ virtual String get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const override;
+ GDVIRTUAL2RC(String, _get_export_option_warning, Ref<EditorExportPreset>, const StringName &);
+
+ virtual String get_os_name() const override;
+ GDVIRTUAL0RC(String, _get_os_name);
+
+ virtual String get_name() const override;
+ GDVIRTUAL0RC(String, _get_name);
+
+ virtual Ref<Texture2D> get_logo() const override;
+ GDVIRTUAL0RC(Ref<Texture2D>, _get_logo);
+
+ virtual bool poll_export() override;
+ GDVIRTUAL0R(bool, _poll_export);
+
+ virtual int get_options_count() const override;
+ GDVIRTUAL0RC(int, _get_options_count);
+
+ virtual String get_options_tooltip() const override;
+ GDVIRTUAL0RC(String, _get_options_tooltip);
+
+ virtual Ref<ImageTexture> get_option_icon(int p_index) const override;
+ GDVIRTUAL1RC(Ref<ImageTexture>, _get_option_icon, int);
+
+ virtual String get_option_label(int p_device) const override;
+ GDVIRTUAL1RC(String, _get_option_label, int);
+
+ virtual String get_option_tooltip(int p_device) const override;
+ GDVIRTUAL1RC(String, _get_option_tooltip, int);
+
+ virtual String get_device_architecture(int p_device) const override;
+ GDVIRTUAL1RC(String, _get_device_architecture, int);
+
+ virtual void cleanup() override;
+ GDVIRTUAL0(_cleanup);
+
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
+ GDVIRTUAL3R(Error, _run, Ref<EditorExportPreset>, int, BitField<EditorExportPlatform::DebugFlags>);
+
+ virtual Ref<Texture2D> get_run_icon() const override;
+ GDVIRTUAL0RC(Ref<Texture2D>, _get_run_icon);
+
+ void set_config_error(const String &p_error) const {
+ config_error = p_error;
+ }
+ String get_config_error() const {
+ return config_error;
+ }
+
+ void set_config_missing_templates(bool p_missing_templates) const {
+ config_missing_templates = p_missing_templates;
+ }
+ bool get_config_missing_templates() const {
+ return config_missing_templates;
+ }
+
+ virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
+ GDVIRTUAL2RC(bool, _can_export, Ref<EditorExportPreset>, bool);
+
+ virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
+ GDVIRTUAL2RC(bool, _has_valid_export_configuration, Ref<EditorExportPreset>, bool);
+
+ virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
+ GDVIRTUAL1RC(bool, _has_valid_project_configuration, Ref<EditorExportPreset>);
+
+ virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
+ GDVIRTUAL1RC(Vector<String>, _get_binary_extensions, Ref<EditorExportPreset>);
+
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+ GDVIRTUAL4R(Error, _export_project, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>);
+
+ virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+ GDVIRTUAL4R(Error, _export_pack, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>);
+
+ virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+ GDVIRTUAL4R(Error, _export_zip, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>);
+
+ virtual void get_platform_features(List<String> *r_features) const override;
+ GDVIRTUAL0RC(Vector<String>, _get_platform_features);
+
+ virtual String get_debug_protocol() const override;
+ GDVIRTUAL0RC(String, _get_debug_protocol);
+
+ EditorExportPlatformExtension();
+ ~EditorExportPlatformExtension();
+};
+
+#endif // EDITOR_EXPORT_PLATFORM_EXTENSION_H
diff --git a/editor/export/editor_export_platform_pc.cpp b/editor/export/editor_export_platform_pc.cpp
index cdaf18b346..24d89b7f34 100644
--- a/editor/export/editor_export_platform_pc.cpp
+++ b/editor/export/editor_export_platform_pc.cpp
@@ -115,7 +115,7 @@ bool EditorExportPlatformPC::has_valid_project_configuration(const Ref<EditorExp
return true;
}
-Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
Error err = prepare_template(p_preset, p_debug, p_path, p_flags);
@@ -129,7 +129,7 @@ Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_pr
return err;
}
-Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
if (!DirAccess::exists(p_path.get_base_dir())) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Template"), TTR("The given export path doesn't exist."));
return ERR_FILE_BAD_PATH;
@@ -182,7 +182,7 @@ Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_
return err;
}
-Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
String pck_path;
if (p_preset->get("binary_format/embed_pck")) {
pck_path = p_path;
diff --git a/editor/export/editor_export_platform_pc.h b/editor/export/editor_export_platform_pc.h
index 53574c2333..668ddaf47e 100644
--- a/editor/export/editor_export_platform_pc.h
+++ b/editor/export/editor_export_platform_pc.h
@@ -54,13 +54,13 @@ public:
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
virtual String get_template_file_name(const String &p_target, const String &p_arch) const = 0;
- virtual Error prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags);
- virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { return OK; };
- virtual Error export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags);
+ virtual Error prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags);
+ virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { return OK; }
+ virtual Error export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags);
void set_name(const String &p_name);
void set_os_name(const String &p_name);
diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp
index 28d0750d5a..f56dd61e3b 100644
--- a/editor/export/editor_export_plugin.cpp
+++ b/editor/export/editor_export_plugin.cpp
@@ -48,6 +48,14 @@ Ref<EditorExportPreset> EditorExportPlugin::get_export_preset() const {
return export_preset;
}
+Ref<EditorExportPlatform> EditorExportPlugin::get_export_platform() const {
+ if (export_preset.is_valid()) {
+ return export_preset->get_platform();
+ } else {
+ return Ref<EditorExportPlatform>();
+ }
+}
+
void EditorExportPlugin::add_file(const String &p_path, const Vector<uint8_t> &p_file, bool p_remap) {
ExtraFile ef;
ef.data = p_file;
@@ -321,6 +329,9 @@ void EditorExportPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("skip"), &EditorExportPlugin::skip);
ClassDB::bind_method(D_METHOD("get_option", "name"), &EditorExportPlugin::get_option);
+ ClassDB::bind_method(D_METHOD("get_export_preset"), &EditorExportPlugin::get_export_preset);
+ ClassDB::bind_method(D_METHOD("get_export_platform"), &EditorExportPlugin::get_export_platform);
+
GDVIRTUAL_BIND(_export_file, "path", "type", "features");
GDVIRTUAL_BIND(_export_begin, "features", "is_debug", "path", "flags");
GDVIRTUAL_BIND(_export_end);
diff --git a/editor/export/editor_export_plugin.h b/editor/export/editor_export_plugin.h
index 56eea85010..7a355614c7 100644
--- a/editor/export/editor_export_plugin.h
+++ b/editor/export/editor_export_plugin.h
@@ -91,6 +91,7 @@ class EditorExportPlugin : public RefCounted {
protected:
void set_export_preset(const Ref<EditorExportPreset> &p_preset);
Ref<EditorExportPreset> get_export_preset() const;
+ Ref<EditorExportPlatform> get_export_platform() const;
void add_file(const String &p_path, const Vector<uint8_t> &p_file, bool p_remap);
void add_shared_object(const String &p_path, const Vector<String> &tags, const String &p_target = String());
diff --git a/editor/export/editor_export_preset.cpp b/editor/export/editor_export_preset.cpp
index e2e3e9d154..9f805666d0 100644
--- a/editor/export/editor_export_preset.cpp
+++ b/editor/export/editor_export_preset.cpp
@@ -62,6 +62,48 @@ bool EditorExportPreset::_get(const StringName &p_name, Variant &r_ret) const {
void EditorExportPreset::_bind_methods() {
ClassDB::bind_method(D_METHOD("_get_property_warning", "name"), &EditorExportPreset::_get_property_warning);
+
+ ClassDB::bind_method(D_METHOD("has", "property"), &EditorExportPreset::has);
+
+ ClassDB::bind_method(D_METHOD("get_files_to_export"), &EditorExportPreset::get_files_to_export);
+ ClassDB::bind_method(D_METHOD("get_customized_files"), &EditorExportPreset::get_customized_files);
+ ClassDB::bind_method(D_METHOD("get_customized_files_count"), &EditorExportPreset::get_customized_files_count);
+ ClassDB::bind_method(D_METHOD("has_export_file", "path"), &EditorExportPreset::has_export_file);
+ ClassDB::bind_method(D_METHOD("get_file_export_mode", "path", "default"), &EditorExportPreset::get_file_export_mode, DEFVAL(MODE_FILE_NOT_CUSTOMIZED));
+
+ ClassDB::bind_method(D_METHOD("get_preset_name"), &EditorExportPreset::get_name);
+ ClassDB::bind_method(D_METHOD("is_runnable"), &EditorExportPreset::is_runnable);
+ ClassDB::bind_method(D_METHOD("are_advanced_options_enabled"), &EditorExportPreset::are_advanced_options_enabled);
+ ClassDB::bind_method(D_METHOD("is_dedicated_server"), &EditorExportPreset::is_dedicated_server);
+ ClassDB::bind_method(D_METHOD("get_export_filter"), &EditorExportPreset::get_export_filter);
+ ClassDB::bind_method(D_METHOD("get_include_filter"), &EditorExportPreset::get_include_filter);
+ ClassDB::bind_method(D_METHOD("get_exclude_filter"), &EditorExportPreset::get_exclude_filter);
+ ClassDB::bind_method(D_METHOD("get_custom_features"), &EditorExportPreset::get_custom_features);
+ ClassDB::bind_method(D_METHOD("get_export_path"), &EditorExportPreset::get_export_path);
+ ClassDB::bind_method(D_METHOD("get_encryption_in_filter"), &EditorExportPreset::get_enc_in_filter);
+ ClassDB::bind_method(D_METHOD("get_encryption_ex_filter"), &EditorExportPreset::get_enc_ex_filter);
+ ClassDB::bind_method(D_METHOD("get_encrypt_pck"), &EditorExportPreset::get_enc_pck);
+ ClassDB::bind_method(D_METHOD("get_encrypt_directory"), &EditorExportPreset::get_enc_directory);
+ ClassDB::bind_method(D_METHOD("get_encryption_key"), &EditorExportPreset::get_script_encryption_key);
+ ClassDB::bind_method(D_METHOD("get_script_export_mode"), &EditorExportPreset::get_script_export_mode);
+
+ ClassDB::bind_method(D_METHOD("get_or_env", "name", "env_var"), &EditorExportPreset::_get_or_env);
+ ClassDB::bind_method(D_METHOD("get_version", "name", "windows_version"), &EditorExportPreset::get_version);
+
+ BIND_ENUM_CONSTANT(EXPORT_ALL_RESOURCES);
+ BIND_ENUM_CONSTANT(EXPORT_SELECTED_SCENES);
+ BIND_ENUM_CONSTANT(EXPORT_SELECTED_RESOURCES);
+ BIND_ENUM_CONSTANT(EXCLUDE_SELECTED_RESOURCES);
+ BIND_ENUM_CONSTANT(EXPORT_CUSTOMIZED);
+
+ BIND_ENUM_CONSTANT(MODE_FILE_NOT_CUSTOMIZED);
+ BIND_ENUM_CONSTANT(MODE_FILE_STRIP);
+ BIND_ENUM_CONSTANT(MODE_FILE_KEEP);
+ BIND_ENUM_CONSTANT(MODE_FILE_REMOVE);
+
+ BIND_ENUM_CONSTANT(MODE_SCRIPT_TEXT);
+ BIND_ENUM_CONSTANT(MODE_SCRIPT_BINARY_TOKENS);
+ BIND_ENUM_CONSTANT(MODE_SCRIPT_BINARY_TOKENS_COMPRESSED);
}
String EditorExportPreset::_get_property_warning(const StringName &p_name) const {
diff --git a/editor/export/editor_export_preset.h b/editor/export/editor_export_preset.h
index c6a8808af1..f220477461 100644
--- a/editor/export/editor_export_preset.h
+++ b/editor/export/editor_export_preset.h
@@ -168,6 +168,9 @@ public:
void set_script_export_mode(int p_mode);
int get_script_export_mode() const;
+ Variant _get_or_env(const StringName &p_name, const String &p_env_var) const {
+ return get_or_env(p_name, p_env_var);
+ }
Variant get_or_env(const StringName &p_name, const String &p_env_var, bool *r_valid = nullptr) const;
// Return the preset's version number, or fall back to the
@@ -183,4 +186,8 @@ public:
EditorExportPreset();
};
+VARIANT_ENUM_CAST(EditorExportPreset::ExportFilter);
+VARIANT_ENUM_CAST(EditorExportPreset::FileExportMode);
+VARIANT_ENUM_CAST(EditorExportPreset::ScriptExportMode);
+
#endif // EDITOR_EXPORT_PRESET_H
diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp
index 9dfbe515d2..2e4f2da9a8 100644
--- a/editor/export/export_template_manager.cpp
+++ b/editor/export/export_template_manager.cpp
@@ -47,6 +47,32 @@
#include "scene/gui/tree.h"
#include "scene/main/http_request.h"
+enum DownloadsAvailability {
+ DOWNLOADS_AVAILABLE,
+ DOWNLOADS_NOT_AVAILABLE_IN_OFFLINE_MODE,
+ DOWNLOADS_NOT_AVAILABLE_FOR_DEV_BUILDS,
+};
+
+static DownloadsAvailability _get_downloads_availability() {
+ const int network_mode = EDITOR_GET("network/connection/network_mode");
+ if (network_mode == EditorSettings::NETWORK_OFFLINE) {
+ return DOWNLOADS_NOT_AVAILABLE_IN_OFFLINE_MODE;
+ }
+
+ // Downloadable export templates are only available for stable and official alpha/beta/RC builds
+ // (which always have a number following their status, e.g. "alpha1").
+ // Therefore, don't display download-related features when using a development version
+ // (whose builds aren't numbered).
+ if (String(VERSION_STATUS) == String("dev") ||
+ String(VERSION_STATUS) == String("alpha") ||
+ String(VERSION_STATUS) == String("beta") ||
+ String(VERSION_STATUS) == String("rc")) {
+ return DOWNLOADS_NOT_AVAILABLE_FOR_DEV_BUILDS;
+ }
+
+ return DOWNLOADS_AVAILABLE;
+}
+
void ExportTemplateManager::_update_template_status() {
// Fetch installed templates from the file system.
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
@@ -641,9 +667,55 @@ void ExportTemplateManager::_open_template_folder(const String &p_version) {
void ExportTemplateManager::popup_manager() {
_update_template_status();
- if (downloads_available && !is_downloading_templates) {
- _refresh_mirrors();
+
+ switch (_get_downloads_availability()) {
+ case DOWNLOADS_AVAILABLE: {
+ current_missing_label->set_text(TTR("Export templates are missing. Download them or install from a file."));
+
+ mirrors_list->clear();
+ mirrors_list->add_item(TTR("Best available mirror"), 0);
+ mirrors_list->set_disabled(false);
+ mirrors_list->set_tooltip_text("");
+
+ mirror_options_button->set_disabled(false);
+
+ download_current_button->set_disabled(false);
+ download_current_button->set_tooltip_text("");
+
+ if (!is_downloading_templates) {
+ _refresh_mirrors();
+ }
+ } break;
+
+ case DOWNLOADS_NOT_AVAILABLE_IN_OFFLINE_MODE: {
+ current_missing_label->set_text(TTR("Export templates are missing. Install them from a file."));
+
+ mirrors_list->clear();
+ mirrors_list->add_item(TTR("Not available in offline mode"), 0);
+ mirrors_list->set_disabled(true);
+ mirrors_list->set_tooltip_text(TTR("Template downloading is disabled in offline mode."));
+
+ mirror_options_button->set_disabled(true);
+
+ download_current_button->set_disabled(true);
+ download_current_button->set_tooltip_text(TTR("Template downloading is disabled in offline mode."));
+ } break;
+
+ case DOWNLOADS_NOT_AVAILABLE_FOR_DEV_BUILDS: {
+ current_missing_label->set_text(TTR("Export templates are missing. Install them from a file."));
+
+ mirrors_list->clear();
+ mirrors_list->add_item(TTR("No templates for development builds"), 0);
+ mirrors_list->set_disabled(true);
+ mirrors_list->set_tooltip_text(TTR("Official export templates aren't available for development builds."));
+
+ mirror_options_button->set_disabled(true);
+
+ download_current_button->set_disabled(true);
+ download_current_button->set_tooltip_text(TTR("Official export templates aren't available for development builds."));
+ } break;
}
+
popup_centered(Size2(720, 280) * EDSCALE);
}
@@ -866,16 +938,6 @@ ExportTemplateManager::ExportTemplateManager() {
set_hide_on_ok(false);
set_ok_button_text(TTR("Close"));
- // Downloadable export templates are only available for stable and official alpha/beta/RC builds
- // (which always have a number following their status, e.g. "alpha1").
- // Therefore, don't display download-related features when using a development version
- // (whose builds aren't numbered).
- downloads_available =
- String(VERSION_STATUS) != String("dev") &&
- String(VERSION_STATUS) != String("alpha") &&
- String(VERSION_STATUS) != String("beta") &&
- String(VERSION_STATUS) != String("rc");
-
VBoxContainer *main_vb = memnew(VBoxContainer);
add_child(main_vb);
@@ -898,11 +960,6 @@ ExportTemplateManager::ExportTemplateManager() {
current_missing_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
current_missing_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
- if (downloads_available) {
- current_missing_label->set_text(TTR("Export templates are missing. Download them or install from a file."));
- } else {
- current_missing_label->set_text(TTR("Export templates are missing. Install them from a file."));
- }
current_hb->add_child(current_missing_label);
// Status: Current version is installed.
@@ -957,12 +1014,6 @@ ExportTemplateManager::ExportTemplateManager() {
mirrors_list = memnew(OptionButton);
mirrors_list->set_custom_minimum_size(Size2(280, 0) * EDSCALE);
- if (downloads_available) {
- mirrors_list->add_item(TTR("Best available mirror"), 0);
- } else {
- mirrors_list->add_item(TTR("(no templates for development builds)"), 0);
- mirrors_list->set_disabled(true);
- }
download_install_hb->add_child(mirrors_list);
request_mirrors = memnew(HTTPRequest);
@@ -972,24 +1023,17 @@ ExportTemplateManager::ExportTemplateManager() {
mirror_options_button = memnew(MenuButton);
mirror_options_button->get_popup()->add_item(TTR("Open in Web Browser"), VISIT_WEB_MIRROR);
mirror_options_button->get_popup()->add_item(TTR("Copy Mirror URL"), COPY_MIRROR_URL);
- mirror_options_button->set_disabled(!downloads_available);
download_install_hb->add_child(mirror_options_button);
mirror_options_button->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ExportTemplateManager::_mirror_options_button_cbk));
download_install_hb->add_spacer();
- Button *download_current_button = memnew(Button);
+ download_current_button = memnew(Button);
download_current_button->set_text(TTR("Download and Install"));
download_current_button->set_tooltip_text(TTR("Download and install templates for the current version from the best possible mirror."));
download_install_hb->add_child(download_current_button);
download_current_button->connect(SceneStringName(pressed), callable_mp(this, &ExportTemplateManager::_download_current));
- // Update downloads buttons to prevent unsupported downloads.
- if (!downloads_available) {
- download_current_button->set_disabled(true);
- download_current_button->set_tooltip_text(TTR("Official export templates aren't available for development builds."));
- }
-
HBoxContainer *install_file_hb = memnew(HBoxContainer);
install_file_hb->set_alignment(BoxContainer::ALIGNMENT_END);
install_options_vb->add_child(install_file_hb);
diff --git a/editor/export/export_template_manager.h b/editor/export/export_template_manager.h
index 5227e43a6e..ccfe568d32 100644
--- a/editor/export/export_template_manager.h
+++ b/editor/export/export_template_manager.h
@@ -46,7 +46,6 @@ class ExportTemplateManager : public AcceptDialog {
GDCLASS(ExportTemplateManager, AcceptDialog);
bool current_version_exists = false;
- bool downloads_available = true;
bool mirrors_available = false;
bool is_refreshing_mirrors = false;
bool is_downloading_templates = false;
@@ -74,6 +73,7 @@ class ExportTemplateManager : public AcceptDialog {
Label *download_progress_label = nullptr;
HTTPRequest *download_templates = nullptr;
Button *install_file_button = nullptr;
+ Button *download_current_button = nullptr;
HTTPRequest *request_mirrors = nullptr;
enum TemplatesAction {
diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp
index 351afa3810..03e9fba12d 100644
--- a/editor/export/project_export.cpp
+++ b/editor/export/project_export.cpp
@@ -1410,12 +1410,12 @@ ProjectExportDialog::ProjectExportDialog() {
sec_scroll_container->add_child(sec_vb);
enc_pck = memnew(CheckButton);
- enc_pck->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_pck_changed));
+ enc_pck->connect(SceneStringName(toggled), callable_mp(this, &ProjectExportDialog::_enc_pck_changed));
enc_pck->set_text(TTR("Encrypt Exported PCK"));
sec_vb->add_child(enc_pck);
enc_directory = memnew(CheckButton);
- enc_directory->connect("toggled", callable_mp(this, &ProjectExportDialog::_enc_directory_changed));
+ enc_directory->connect(SceneStringName(toggled), callable_mp(this, &ProjectExportDialog::_enc_directory_changed));
enc_directory->set_text(TTR("Encrypt Index (File Names and Info)"));
sec_vb->add_child(enc_directory);
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index a9de8e3bc5..bfb7123925 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -1216,7 +1216,7 @@ void FileSystemDock::_select_file(const String &p_path, bool p_select_in_favorit
}
if (is_imported) {
- SceneImportSettingsDialog::get_singleton()->open_settings(p_path, resource_type == "AnimationLibrary");
+ SceneImportSettingsDialog::get_singleton()->open_settings(p_path, resource_type);
} else if (resource_type == "PackedScene") {
EditorNode::get_singleton()->open_request(fpath);
} else {
diff --git a/editor/groups_editor.cpp b/editor/groups_editor.cpp
index a5f7e8556c..ce2dbe7cb1 100644
--- a/editor/groups_editor.cpp
+++ b/editor/groups_editor.cpp
@@ -648,7 +648,7 @@ void GroupsEditor::_show_add_group_dialog() {
add_group_description->set_editable(false);
gc->add_child(add_group_description);
- global_group_button->connect("toggled", callable_mp(add_group_description, &LineEdit::set_editable));
+ global_group_button->connect(SceneStringName(toggled), callable_mp(add_group_description, &LineEdit::set_editable));
add_group_dialog->register_text_enter(add_group_name);
add_group_dialog->register_text_enter(add_group_description);
diff --git a/editor/gui/editor_bottom_panel.cpp b/editor/gui/editor_bottom_panel.cpp
index 3cb95a3926..4b2fd9cb2f 100644
--- a/editor/gui/editor_bottom_panel.cpp
+++ b/editor/gui/editor_bottom_panel.cpp
@@ -158,7 +158,7 @@ void EditorBottomPanel::load_layout_from_config(Ref<ConfigFile> p_config_file, c
Button *EditorBottomPanel::add_item(String p_text, Control *p_item, const Ref<Shortcut> &p_shortcut, bool p_at_front) {
Button *tb = memnew(Button);
tb->set_theme_type_variation("BottomPanelButton");
- tb->connect("toggled", callable_mp(this, &EditorBottomPanel::_switch_by_control).bind(p_item));
+ tb->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_switch_by_control).bind(p_item));
tb->set_drag_forwarding(Callable(), callable_mp(this, &EditorBottomPanel::_button_drag_hover).bind(tb, p_item), Callable());
tb->set_text(p_text);
tb->set_shortcut(p_shortcut);
@@ -295,5 +295,5 @@ EditorBottomPanel::EditorBottomPanel() {
expand_button->set_theme_type_variation("FlatMenuButton");
expand_button->set_toggle_mode(true);
expand_button->set_shortcut(ED_SHORTCUT_AND_COMMAND("editor/bottom_panel_expand", TTR("Expand Bottom Panel"), KeyModifierMask::SHIFT | Key::F12));
- expand_button->connect("toggled", callable_mp(this, &EditorBottomPanel::_expand_button_toggled));
+ expand_button->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_expand_button_toggled));
}
diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp
index afc6d58d63..7aa19509e1 100644
--- a/editor/gui/editor_file_dialog.cpp
+++ b/editor/gui/editor_file_dialog.cpp
@@ -1779,7 +1779,7 @@ void EditorFileDialog::_update_option_controls() {
CheckBox *cb = memnew(CheckBox);
cb->set_pressed(opt.default_idx);
grid_options->add_child(cb);
- cb->connect("toggled", callable_mp(this, &EditorFileDialog::_option_changed_checkbox_toggled).bind(opt.name));
+ cb->connect(SceneStringName(toggled), callable_mp(this, &EditorFileDialog::_option_changed_checkbox_toggled).bind(opt.name));
selected_options[opt.name] = (bool)opt.default_idx;
} else {
OptionButton *ob = memnew(OptionButton);
@@ -2146,7 +2146,7 @@ EditorFileDialog::EditorFileDialog() {
show_hidden->set_toggle_mode(true);
show_hidden->set_pressed(is_showing_hidden_files());
show_hidden->set_tooltip_text(TTR("Toggle the visibility of hidden files."));
- show_hidden->connect("toggled", callable_mp(this, &EditorFileDialog::set_show_hidden_files));
+ show_hidden->connect(SceneStringName(toggled), callable_mp(this, &EditorFileDialog::set_show_hidden_files));
pathhb->add_child(show_hidden);
pathhb->add_child(memnew(VSeparator));
diff --git a/editor/gui/editor_run_bar.cpp b/editor/gui/editor_run_bar.cpp
index 4cc2d1145e..9050ee0cd4 100644
--- a/editor/gui/editor_run_bar.cpp
+++ b/editor/gui/editor_run_bar.cpp
@@ -445,7 +445,7 @@ EditorRunBar::EditorRunBar() {
write_movie_button->set_pressed(false);
write_movie_button->set_focus_mode(Control::FOCUS_NONE);
write_movie_button->set_tooltip_text(TTR("Enable Movie Maker mode.\nThe project will run at stable FPS and the visual and audio output will be recorded to a video file."));
- write_movie_button->connect("toggled", callable_mp(this, &EditorRunBar::_write_movie_toggled));
+ write_movie_button->connect(SceneStringName(toggled), callable_mp(this, &EditorRunBar::_write_movie_toggled));
quick_run = memnew(EditorQuickOpen);
add_child(quick_run);
diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp
index 9f9bdb37b3..5372d33b4c 100644
--- a/editor/gui/editor_spin_slider.cpp
+++ b/editor/gui/editor_spin_slider.cpp
@@ -35,6 +35,7 @@
#include "core/os/keyboard.h"
#include "editor/editor_settings.h"
#include "editor/themes/editor_scale.h"
+#include "scene/theme/theme_db.h"
bool EditorSpinSlider::is_text_field() const {
return true;
@@ -383,7 +384,7 @@ void EditorSpinSlider::_draw_spin_slider() {
if (!hide_slider) {
if (get_step() == 1) {
- Ref<Texture2D> updown2 = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox"));
+ Ref<Texture2D> updown2 = is_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);
@@ -701,6 +702,9 @@ void EditorSpinSlider::_bind_methods() {
ADD_SIGNAL(MethodInfo("ungrabbed"));
ADD_SIGNAL(MethodInfo("value_focus_entered"));
ADD_SIGNAL(MethodInfo("value_focus_exited"));
+
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, EditorSpinSlider, updown_icon, "updown");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, EditorSpinSlider, updown_disabled_icon, "updown_disabled");
}
void EditorSpinSlider::_ensure_input_popup() {
diff --git a/editor/gui/editor_spin_slider.h b/editor/gui/editor_spin_slider.h
index a0c0685629..2476c2f71b 100644
--- a/editor/gui/editor_spin_slider.h
+++ b/editor/gui/editor_spin_slider.h
@@ -87,6 +87,11 @@ class EditorSpinSlider : public Range {
void _ensure_input_popup();
void _draw_spin_slider();
+ struct ThemeCache {
+ Ref<Texture2D> updown_icon;
+ Ref<Texture2D> updown_disabled_icon;
+ } theme_cache;
+
protected:
void _notification(int p_what);
virtual void gui_input(const Ref<InputEvent> &p_event) override;
diff --git a/editor/gui/editor_zoom_widget.cpp b/editor/gui/editor_zoom_widget.cpp
index 341da7bfaf..50a4f020ab 100644
--- a/editor/gui/editor_zoom_widget.cpp
+++ b/editor/gui/editor_zoom_widget.cpp
@@ -141,22 +141,15 @@ void EditorZoomWidget::set_zoom_by_increments(int p_increment_count, bool p_inte
}
}
} else {
- // Base increment factor defined as the twelveth root of two.
- // This allows for a smooth geometric evolution of the zoom, with the advantage of
- // visiting all integer power of two scale factors.
- // Note: this is analogous to the 'semitone' interval in the music world
- // In order to avoid numerical imprecisions, we compute and edit a zoom index
- // with the following relation: zoom = 2 ^ (index / 12)
-
if (zoom < CMP_EPSILON || p_increment_count == 0) {
return;
}
- // zoom = 2**(index/12) => log2(zoom) = index/12
- float closest_zoom_index = Math::round(Math::log(zoom_noscale) * 12.f / Math::log(2.f));
-
- float new_zoom_index = closest_zoom_index + p_increment_count;
- float new_zoom = Math::pow(2.f, new_zoom_index / 12.f);
+ // Zoom is calculated as pow(zoom_factor, zoom_step).
+ // This ensures the zoom will always equal 100% when zoom_step is 0.
+ float zoom_factor = EDITOR_GET("editors/2d/zoom_speed_factor");
+ float current_zoom_step = Math::round(Math::log(zoom_noscale) / Math::log(zoom_factor));
+ float new_zoom = Math::pow(zoom_factor, current_zoom_step + p_increment_count);
// Restore Editor scale transformation.
new_zoom *= MAX(1, EDSCALE);
diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp
index 69ab9810a0..52ba98b4d5 100644
--- a/editor/gui/scene_tree_editor.cpp
+++ b/editor/gui/scene_tree_editor.cpp
@@ -1769,7 +1769,7 @@ SceneTreeDialog::SceneTreeDialog() {
// Add 'Show All' button to HBoxContainer next to the filter, visible only when valid_types is defined.
show_all_nodes = memnew(CheckButton);
show_all_nodes->set_text(TTR("Show All"));
- show_all_nodes->connect("toggled", callable_mp(this, &SceneTreeDialog::_show_all_nodes_changed));
+ show_all_nodes->connect(SceneStringName(toggled), callable_mp(this, &SceneTreeDialog::_show_all_nodes_changed));
show_all_nodes->set_h_size_flags(Control::SIZE_SHRINK_BEGIN);
show_all_nodes->hide();
filter_hbc->add_child(show_all_nodes);
diff --git a/editor/history_dock.cpp b/editor/history_dock.cpp
index 5a64fba788..1a0971a15c 100644
--- a/editor/history_dock.cpp
+++ b/editor/history_dock.cpp
@@ -245,8 +245,8 @@ HistoryDock::HistoryDock() {
current_scene_checkbox->set_text(TTR("Scene"));
current_scene_checkbox->set_h_size_flags(SIZE_EXPAND_FILL);
current_scene_checkbox->set_clip_text(true);
- current_scene_checkbox->connect("toggled", callable_mp(this, &HistoryDock::refresh_history).unbind(1));
- current_scene_checkbox->connect("toggled", callable_mp(this, &HistoryDock::save_options).unbind(1));
+ current_scene_checkbox->connect(SceneStringName(toggled), callable_mp(this, &HistoryDock::refresh_history).unbind(1));
+ current_scene_checkbox->connect(SceneStringName(toggled), callable_mp(this, &HistoryDock::save_options).unbind(1));
global_history_checkbox = memnew(CheckBox);
mode_hb->add_child(global_history_checkbox);
@@ -255,8 +255,8 @@ HistoryDock::HistoryDock() {
global_history_checkbox->set_text(TTR("Global"));
global_history_checkbox->set_h_size_flags(SIZE_EXPAND_FILL);
global_history_checkbox->set_clip_text(true);
- global_history_checkbox->connect("toggled", callable_mp(this, &HistoryDock::refresh_history).unbind(1));
- global_history_checkbox->connect("toggled", callable_mp(this, &HistoryDock::save_options).unbind(1));
+ global_history_checkbox->connect(SceneStringName(toggled), callable_mp(this, &HistoryDock::refresh_history).unbind(1));
+ global_history_checkbox->connect(SceneStringName(toggled), callable_mp(this, &HistoryDock::save_options).unbind(1));
action_list = memnew(ItemList);
action_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
diff --git a/editor/icons/GuiSpinboxDown.svg b/editor/icons/GuiSpinboxDown.svg
new file mode 100644
index 0000000000..f8f473ce1a
--- /dev/null
+++ b/editor/icons/GuiSpinboxDown.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="8"><path fill="none" stroke="#e0e0e0" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".8" stroke-width="2" d="m12 2-4 4-4-4"/></svg> \ No newline at end of file
diff --git a/editor/icons/GuiSpinboxUp.svg b/editor/icons/GuiSpinboxUp.svg
new file mode 100644
index 0000000000..28bd0505d4
--- /dev/null
+++ b/editor/icons/GuiSpinboxUp.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="8"><path fill="none" stroke="#e0e0e0" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".8" stroke-width="2" d="m4 6 4-4 4 4"/></svg> \ No newline at end of file
diff --git a/editor/icons/MemberConstructor.svg b/editor/icons/MemberConstructor.svg
new file mode 100644
index 0000000000..0e61768739
--- /dev/null
+++ b/editor/icons/MemberConstructor.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M9 6c-1.33 2.67-1.33 4.33 0 7h2c-1.33-2.67-1.33-4.33 0-7zm4 0c1.33 2.67 1.33 4.33 0 7h2c1.33-2.67 1.33-4.33 0-7zM1 11a1 1 0 0 0 0 2 1 1 0 0 0 0-2zm6 2v-2H6a1 1 0 0 1 0-2h1V7H6a3 3 0 0 0 0 6z"/></svg> \ No newline at end of file
diff --git a/editor/icons/MemberOperator.svg b/editor/icons/MemberOperator.svg
new file mode 100644
index 0000000000..a00d990e26
--- /dev/null
+++ b/editor/icons/MemberOperator.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M9 6c-1.33 2.67-1.33 4.33 0 7h2c-1.33-2.67-1.33-4.33 0-7zm4 0c1.33 2.67 1.33 4.33 0 7h2c1.33-2.67 1.33-4.33 0-7zM1 11a1 1 0 0 0 0 2 1 1 0 0 0 0-2zm4-4a3 3 0 0 0 0 6 3 3 0 0 0 0-6zm0 2a1 1 0 0 1 0 2 1 1 0 0 1 0-2z"/></svg> \ No newline at end of file
diff --git a/editor/icons/SnapKeys.svg b/editor/icons/SnapKeys.svg
new file mode 100644
index 0000000000..813781b801
--- /dev/null
+++ b/editor/icons/SnapKeys.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m 7,13 v 2 h 2 v -2 z m 6,0 v 2 h 2 v -2 z"/><path fill="#fff" fill-opacity=".686" d="m 7,13 h 2 v -2 a 2,2 0 0 1 4,0 v 2 h 2 v -2 a 4,4 0 0 0 -8,0 z"/><path fill="#fff" d="M 5,1 1,5 5,9 9,5 Z"/></svg> \ No newline at end of file
diff --git a/editor/icons/SnapTimeline.svg b/editor/icons/SnapTimeline.svg
new file mode 100644
index 0000000000..b558545f5a
--- /dev/null
+++ b/editor/icons/SnapTimeline.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m 7,13 v 2 h 2 v -2 z m 6,0 v 2 h 2 v -2 z"/><path fill="#fff" fill-opacity=".686" d="m 7,13 h 2 v -2 a 2,2 0 0 1 4,0 v 2 h 2 v -2 a 4,4 0 0 0 -8,0 z"/><path fill="#fff" d="m 1,2 3,3 v 9 H 6 V 5 L 9,2 Z"/></svg> \ No newline at end of file
diff --git a/editor/icons/editor_icons_builders.py b/editor/icons/editor_icons_builders.py
index 5cc67ca3ad..d3e8953483 100644
--- a/editor/icons/editor_icons_builders.py
+++ b/editor/icons/editor_icons_builders.py
@@ -3,6 +3,8 @@
import os
from io import StringIO
+from methods import to_raw_cstring
+
# See also `scene/theme/icons/default_theme_icons_builders.py`.
def make_editor_icons_action(target, source, env):
@@ -10,21 +12,9 @@ def make_editor_icons_action(target, source, env):
svg_icons = source
with StringIO() as icons_string, StringIO() as s:
- for f in svg_icons:
- fname = str(f)
-
- icons_string.write('\t"')
-
- with open(fname, "rb") as svgf:
- b = svgf.read(1)
- while len(b) == 1:
- icons_string.write("\\" + str(hex(ord(b)))[1:])
- b = svgf.read(1)
-
- icons_string.write('"')
- if fname != svg_icons[-1]:
- icons_string.write(",")
- icons_string.write("\n")
+ for svg in svg_icons:
+ with open(str(svg), "r") as svgf:
+ icons_string.write("\t%s,\n" % to_raw_cstring(svgf.read()))
s.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
s.write("#ifndef _EDITOR_ICONS_H\n")
diff --git a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp
index b69d38afa0..a8886b3818 100644
--- a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp
+++ b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp
@@ -54,7 +54,7 @@ void PostImportPluginSkeletonRestFixer::get_internal_import_options(InternalImpo
}
}
-Variant PostImportPluginSkeletonRestFixer::get_internal_option_visibility(InternalImportCategory p_category, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
+Variant PostImportPluginSkeletonRestFixer::get_internal_option_visibility(InternalImportCategory p_category, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
if (p_option.begins_with("retarget/rest_fixer/fix_silhouette/")) {
if (!bool(p_options["retarget/rest_fixer/fix_silhouette/enable"])) {
diff --git a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.h b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.h
index 1750ed1233..9fec76be48 100644
--- a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.h
+++ b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.h
@@ -38,7 +38,7 @@ class PostImportPluginSkeletonRestFixer : public EditorScenePostImportPlugin {
public:
virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) override;
- virtual Variant get_internal_option_visibility(InternalImportCategory p_category, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
+ virtual Variant get_internal_option_visibility(InternalImportCategory p_category, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) override;
PostImportPluginSkeletonRestFixer();
diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp
index d0d7142c01..fa07511dd0 100644
--- a/editor/import/3d/resource_importer_scene.cpp
+++ b/editor/import/3d/resource_importer_scene.cpp
@@ -96,9 +96,10 @@ void EditorSceneFormatImporter::get_import_options(const String &p_path, List<Re
GDVIRTUAL_CALL(_get_import_options, p_path);
}
-Variant EditorSceneFormatImporter::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) {
+Variant EditorSceneFormatImporter::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) {
Variant ret;
- GDVIRTUAL_CALL(_get_option_visibility, p_path, p_for_animation, p_option, ret);
+ // For compatibility with the old API, pass the import type as a boolean.
+ GDVIRTUAL_CALL(_get_option_visibility, p_path, p_scene_import_type == "AnimationLibrary", p_option, ret);
return ret;
}
@@ -172,13 +173,16 @@ void EditorScenePostImportPlugin::get_internal_import_options(InternalImportCate
GDVIRTUAL_CALL(_get_internal_import_options, p_category);
current_option_list = nullptr;
}
-Variant EditorScenePostImportPlugin::get_internal_option_visibility(InternalImportCategory p_category, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
+
+Variant EditorScenePostImportPlugin::get_internal_option_visibility(InternalImportCategory p_category, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
current_options = &p_options;
Variant ret;
- GDVIRTUAL_CALL(_get_internal_option_visibility, p_category, p_for_animation, p_option, ret);
+ // For compatibility with the old API, pass the import type as a boolean.
+ GDVIRTUAL_CALL(_get_internal_option_visibility, p_category, p_scene_import_type == "AnimationLibrary", p_option, ret);
current_options = nullptr;
return ret;
}
+
Variant EditorScenePostImportPlugin::get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
current_options = &p_options;
Variant ret;
@@ -198,10 +202,10 @@ void EditorScenePostImportPlugin::get_import_options(const String &p_path, List<
GDVIRTUAL_CALL(_get_import_options, p_path);
current_option_list = nullptr;
}
-Variant EditorScenePostImportPlugin::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
+Variant EditorScenePostImportPlugin::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
current_options = &p_options;
Variant ret;
- GDVIRTUAL_CALL(_get_option_visibility, p_path, p_for_animation, p_option, ret);
+ GDVIRTUAL_CALL(_get_option_visibility, p_path, p_scene_import_type == "AnimationLibrary", p_option, ret);
current_options = nullptr;
return ret;
}
@@ -245,11 +249,22 @@ void EditorScenePostImportPlugin::_bind_methods() {
/////////////////////////////////////////////////////////
String ResourceImporterScene::get_importer_name() const {
- return animation_importer ? "animation_library" : "scene";
+ // For compatibility with 4.2 and earlier we need to keep the "scene" and "animation_library" names.
+ // However this is arbitrary so for new import types we can use any string.
+ if (_scene_import_type == "PackedScene") {
+ return "scene";
+ } else if (_scene_import_type == "AnimationLibrary") {
+ return "animation_library";
+ }
+ return _scene_import_type;
}
String ResourceImporterScene::get_visible_name() const {
- return animation_importer ? "Animation Library" : "Scene";
+ // This is displayed on the UI. Friendly names here are nice but not vital, so fall back to the type.
+ if (_scene_import_type == "PackedScene") {
+ return "Scene";
+ }
+ return _scene_import_type.capitalize();
}
void ResourceImporterScene::get_recognized_extensions(List<String> *p_extensions) const {
@@ -257,11 +272,14 @@ void ResourceImporterScene::get_recognized_extensions(List<String> *p_extensions
}
String ResourceImporterScene::get_save_extension() const {
- return animation_importer ? "res" : "scn";
+ if (_scene_import_type == "PackedScene") {
+ return "scn";
+ }
+ return "res";
}
String ResourceImporterScene::get_resource_type() const {
- return animation_importer ? "AnimationLibrary" : "PackedScene";
+ return _scene_import_type;
}
int ResourceImporterScene::get_format_version() const {
@@ -269,27 +287,28 @@ int ResourceImporterScene::get_format_version() const {
}
bool ResourceImporterScene::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
- if (animation_importer) {
+ if (_scene_import_type == "PackedScene") {
+ if (p_option.begins_with("animation/")) {
+ if (p_option != "animation/import" && !bool(p_options["animation/import"])) {
+ return false;
+ }
+ }
+ } else if (_scene_import_type == "AnimationLibrary") {
if (p_option == "animation/import") { // Option ignored, animation always imported.
return false;
}
- } else if (p_option.begins_with("animation/")) {
- if (p_option != "animation/import" && !bool(p_options["animation/import"])) {
- return false;
+ if (p_option == "nodes/root_type" || p_option == "nodes/root_name" || p_option.begins_with("meshes/") || p_option.begins_with("skins/")) {
+ return false; // Nothing to do here for animations.
}
}
- if (animation_importer && (p_option == "nodes/root_type" || p_option == "nodes/root_name" || p_option.begins_with("meshes/") || p_option.begins_with("skins/"))) {
- return false; // Nothing to do here for animations.
- }
-
if (p_option == "meshes/lightmap_texel_size" && int(p_options["meshes/light_baking"]) != 2) {
// Only display the lightmap texel size import option when using the Static Lightmaps light baking mode.
return false;
}
for (int i = 0; i < post_importer_plugins.size(); i++) {
- Variant ret = post_importer_plugins.write[i]->get_option_visibility(p_path, animation_importer, p_option, p_options);
+ Variant ret = post_importer_plugins.write[i]->get_option_visibility(p_path, _scene_import_type, p_option, p_options);
if (ret.get_type() == Variant::BOOL) {
if (!ret) {
return false;
@@ -298,7 +317,7 @@ bool ResourceImporterScene::get_option_visibility(const String &p_path, const St
}
for (Ref<EditorSceneFormatImporter> importer : scene_importers) {
- Variant ret = importer->get_option_visibility(p_path, animation_importer, p_option, p_options);
+ Variant ret = importer->get_option_visibility(p_path, _scene_import_type, p_option, p_options);
if (ret.get_type() == Variant::BOOL) {
if (!ret) {
return false;
@@ -630,6 +649,9 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap<R
String name = p_node->get_name();
NodePath original_path = p_root->get_path_to(p_node); // Used to detect renames due to import hints.
+ Ref<Resource> original_meta = memnew(Resource); // Create temp resource to hold original meta
+ original_meta->merge_meta_from(p_node);
+
bool isroot = p_node == p_root;
if (!isroot && _teststr(name, "noimp")) {
@@ -1003,6 +1025,8 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap<R
print_verbose(vformat("Fix: Renamed %s to %s", original_path, new_path));
r_node_renames.push_back({ original_path, p_node });
}
+ // If we created new node instead, merge meta values from the original node.
+ p_node->merge_meta_from(*original_meta);
}
return p_node;
@@ -2283,7 +2307,7 @@ bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategor
}
for (int i = 0; i < post_importer_plugins.size(); i++) {
- Variant ret = post_importer_plugins.write[i]->get_internal_option_visibility(EditorScenePostImportPlugin::InternalImportCategory(p_category), animation_importer, p_option, p_options);
+ Variant ret = post_importer_plugins.write[i]->get_internal_option_visibility(EditorScenePostImportPlugin::InternalImportCategory(p_category), _scene_import_type, p_option, p_options);
if (ret.get_type() == Variant::BOOL) {
return ret;
}
@@ -2433,6 +2457,8 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
mesh_node->set_transform(src_mesh_node->get_transform());
mesh_node->set_skin(src_mesh_node->get_skin());
mesh_node->set_skeleton_path(src_mesh_node->get_skeleton_path());
+ mesh_node->merge_meta_from(src_mesh_node);
+
if (src_mesh_node->get_mesh().is_valid()) {
Ref<ArrayMesh> mesh;
if (!src_mesh_node->get_mesh()->has_mesh()) {
@@ -2580,6 +2606,7 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
for (int i = 0; i < mesh->get_surface_count(); i++) {
mesh_node->set_surface_override_material(i, src_mesh_node->get_surface_material(i));
}
+ mesh->merge_meta_from(*src_mesh_node->get_mesh());
}
}
@@ -2877,13 +2904,11 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
int import_flags = 0;
- if (animation_importer) {
+ if (_scene_import_type == "AnimationLibrary") {
import_flags |= EditorSceneFormatImporter::IMPORT_ANIMATION;
import_flags |= EditorSceneFormatImporter::IMPORT_DISCARD_MESHES_AND_MATERIALS;
- } else {
- if (bool(p_options["animation/import"])) {
- import_flags |= EditorSceneFormatImporter::IMPORT_ANIMATION;
- }
+ } else if (bool(p_options["animation/import"])) {
+ import_flags |= EditorSceneFormatImporter::IMPORT_ANIMATION;
}
if (bool(p_options["skins/use_named_skins"])) {
@@ -3079,6 +3104,19 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
}
}
+ // Apply RESET animation before serializing.
+ if (_scene_import_type == "PackedScene") {
+ int scene_child_count = scene->get_child_count();
+ for (int i = 0; i < scene_child_count; i++) {
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(scene->get_child(i));
+ if (ap) {
+ if (ap->can_apply_reset()) {
+ ap->apply_reset();
+ }
+ }
+ }
+ }
+
if (post_import_script.is_valid()) {
post_import_script->init(p_source_file);
scene = post_import_script->post_import(scene);
@@ -3097,11 +3135,11 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
progress.step(TTR("Saving..."), 104);
int flags = 0;
- if (EDITOR_GET("filesystem/on_save/compress_binary_resources")) {
+ if (EditorSettings::get_singleton() && EDITOR_GET("filesystem/on_save/compress_binary_resources")) {
flags |= ResourceSaver::FLAG_COMPRESS;
}
- if (animation_importer) {
+ if (_scene_import_type == "AnimationLibrary") {
Ref<AnimationLibrary> library;
for (int i = 0; i < scene->get_child_count(); i++) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(scene->get_child(i));
@@ -3122,13 +3160,14 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
print_verbose("Saving animation to: " + p_save_path + ".res");
err = ResourceSaver::save(library, p_save_path + ".res", flags); //do not take over, let the changed files reload themselves
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save animation to file '" + p_save_path + ".res'.");
-
- } else {
+ } else if (_scene_import_type == "PackedScene") {
Ref<PackedScene> packer = memnew(PackedScene);
packer->pack(scene);
print_verbose("Saving scene to: " + p_save_path + ".scn");
err = ResourceSaver::save(packer, p_save_path + ".scn", flags); //do not take over, let the changed files reload themselves
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save scene to file '" + p_save_path + ".scn'.");
+ } else {
+ ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, "Unknown scene import type: " + _scene_import_type);
}
memdelete(scene);
@@ -3150,20 +3189,20 @@ bool ResourceImporterScene::has_advanced_options() const {
}
void ResourceImporterScene::show_advanced_options(const String &p_path) {
- SceneImportSettingsDialog::get_singleton()->open_settings(p_path, animation_importer);
+ SceneImportSettingsDialog::get_singleton()->open_settings(p_path, _scene_import_type);
}
-ResourceImporterScene::ResourceImporterScene(bool p_animation_import, bool p_singleton) {
+ResourceImporterScene::ResourceImporterScene(const String &p_scene_import_type, bool p_singleton) {
// This should only be set through the EditorNode.
if (p_singleton) {
- if (p_animation_import) {
+ if (p_scene_import_type == "AnimationLibrary") {
animation_singleton = this;
- } else {
+ } else if (p_scene_import_type == "PackedScene") {
scene_singleton = this;
}
}
- animation_importer = p_animation_import;
+ _scene_import_type = p_scene_import_type;
}
ResourceImporterScene::~ResourceImporterScene() {
diff --git a/editor/import/3d/resource_importer_scene.h b/editor/import/3d/resource_importer_scene.h
index f9124ad289..9759f328d7 100644
--- a/editor/import/3d/resource_importer_scene.h
+++ b/editor/import/3d/resource_importer_scene.h
@@ -44,10 +44,10 @@
#include "scene/resources/animation.h"
#include "scene/resources/mesh.h"
-class Material;
class AnimationPlayer;
-
class ImporterMesh;
+class Material;
+
class EditorSceneFormatImporter : public RefCounted {
GDCLASS(EditorSceneFormatImporter, RefCounted);
@@ -78,7 +78,7 @@ public:
virtual void get_extensions(List<String> *r_extensions) const;
virtual Node *import_scene(const String &p_path, uint32_t p_flags, const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err = nullptr);
virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options);
- virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options);
+ virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options);
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {}
EditorSceneFormatImporter() {}
@@ -140,13 +140,13 @@ public:
void add_import_option_advanced(Variant::Type p_type, const String &p_name, const Variant &p_default_value, PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = String(), int p_usage_flags = PROPERTY_USAGE_DEFAULT);
virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options);
- virtual Variant get_internal_option_visibility(InternalImportCategory p_category, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
+ virtual Variant get_internal_option_visibility(InternalImportCategory p_category, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
virtual Variant get_internal_option_update_view_required(InternalImportCategory p_category, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options);
virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options);
- virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
+ virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) const;
virtual void pre_process(Node *p_scene, const HashMap<StringName, Variant> &p_options);
virtual void post_process(Node *p_scene, const HashMap<StringName, Variant> &p_options);
@@ -237,7 +237,7 @@ class ResourceImporterScene : public ResourceImporter {
void _optimize_track_usage(AnimationPlayer *p_player, AnimationImportTracks *p_track_actions);
- bool animation_importer = false;
+ String _scene_import_type = "PackedScene";
public:
static ResourceImporterScene *get_scene_singleton() { return scene_singleton; }
@@ -253,6 +253,9 @@ public:
static void clean_up_importer_plugins();
+ String get_scene_import_type() const { return _scene_import_type; }
+ void set_scene_import_type(const String &p_type) { _scene_import_type = p_type; }
+
virtual String get_importer_name() const override;
virtual String get_visible_name() const override;
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
@@ -303,7 +306,7 @@ public:
virtual bool can_import_threaded() const override { return false; }
- ResourceImporterScene(bool p_animation_import = false, bool p_singleton = false);
+ ResourceImporterScene(const String &p_scene_import_type = "PackedScene", bool p_singleton = false);
~ResourceImporterScene();
template <typename M>
diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp
index 7cd5279b63..7ca3cb6c3a 100644
--- a/editor/import/3d/scene_import_settings.cpp
+++ b/editor/import/3d/scene_import_settings.cpp
@@ -678,26 +678,26 @@ void SceneImportSettingsDialog::update_view() {
update_view_timer->start();
}
-void SceneImportSettingsDialog::open_settings(const String &p_path, bool p_for_animation) {
+void SceneImportSettingsDialog::open_settings(const String &p_path, const String &p_scene_import_type) {
if (scene) {
_cleanup();
memdelete(scene);
scene = nullptr;
}
- editing_animation = p_for_animation;
+ editing_animation = p_scene_import_type == "AnimationLibrary";
scene_import_settings_data->settings = nullptr;
scene_import_settings_data->path = p_path;
// Visibility.
- data_mode->set_tab_hidden(1, p_for_animation);
- data_mode->set_tab_hidden(2, p_for_animation);
- if (p_for_animation) {
+ data_mode->set_tab_hidden(1, editing_animation);
+ data_mode->set_tab_hidden(2, editing_animation);
+ if (editing_animation) {
data_mode->set_current_tab(0);
}
- action_menu->get_popup()->set_item_disabled(action_menu->get_popup()->get_item_id(ACTION_EXTRACT_MATERIALS), p_for_animation);
- action_menu->get_popup()->set_item_disabled(action_menu->get_popup()->get_item_id(ACTION_CHOOSE_MESH_SAVE_PATHS), p_for_animation);
+ action_menu->get_popup()->set_item_disabled(action_menu->get_popup()->get_item_id(ACTION_EXTRACT_MATERIALS), editing_animation);
+ action_menu->get_popup()->set_item_disabled(action_menu->get_popup()->get_item_id(ACTION_CHOOSE_MESH_SAVE_PATHS), editing_animation);
base_path = p_path;
@@ -771,7 +771,7 @@ void SceneImportSettingsDialog::open_settings(const String &p_path, bool p_for_a
// Start with the root item (Scene) selected.
scene_tree->get_root()->select(0);
- if (p_for_animation) {
+ if (editing_animation) {
set_title(vformat(TTR("Advanced Import Settings for AnimationLibrary '%s'"), base_path.get_file()));
} else {
set_title(vformat(TTR("Advanced Import Settings for Scene '%s'"), base_path.get_file()));
diff --git a/editor/import/3d/scene_import_settings.h b/editor/import/3d/scene_import_settings.h
index c2a5151432..bbd0d2c22d 100644
--- a/editor/import/3d/scene_import_settings.h
+++ b/editor/import/3d/scene_import_settings.h
@@ -243,7 +243,7 @@ public:
bool is_editing_animation() const { return editing_animation; }
void request_generate_collider();
void update_view();
- void open_settings(const String &p_path, bool p_for_animation = false);
+ void open_settings(const String &p_path, const String &p_scene_import_type = "PackedScene");
static SceneImportSettingsDialog *get_singleton();
Node *get_selected_node();
SceneImportSettingsDialog();
diff --git a/editor/import/audio_stream_import_settings.cpp b/editor/import/audio_stream_import_settings.cpp
index a53deefee9..9a0c62193c 100644
--- a/editor/import/audio_stream_import_settings.cpp
+++ b/editor/import/audio_stream_import_settings.cpp
@@ -537,7 +537,7 @@ AudioStreamImportSettingsDialog::AudioStreamImportSettingsDialog() {
loop = memnew(CheckBox);
loop->set_text(TTR("Enable"));
loop->set_tooltip_text(TTR("Enable looping."));
- loop->connect("toggled", callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
+ loop->connect(SceneStringName(toggled), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
loop_hb->add_child(loop);
loop_hb->add_spacer();
loop_hb->add_child(memnew(Label(TTR("Offset:"))));
@@ -554,7 +554,7 @@ AudioStreamImportSettingsDialog::AudioStreamImportSettingsDialog() {
interactive_hb->add_theme_constant_override("separation", 4 * EDSCALE);
bpm_enabled = memnew(CheckBox);
bpm_enabled->set_text((TTR("BPM:")));
- bpm_enabled->connect("toggled", callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
+ bpm_enabled->connect(SceneStringName(toggled), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
interactive_hb->add_child(bpm_enabled);
bpm_edit = memnew(SpinBox);
bpm_edit->set_max(400);
@@ -565,7 +565,7 @@ AudioStreamImportSettingsDialog::AudioStreamImportSettingsDialog() {
interactive_hb->add_spacer();
beats_enabled = memnew(CheckBox);
beats_enabled->set_text(TTR("Beat Count:"));
- beats_enabled->connect("toggled", callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
+ beats_enabled->connect(SceneStringName(toggled), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
interactive_hb->add_child(beats_enabled);
beats_edit = memnew(SpinBox);
beats_edit->set_tooltip_text(TTR("Configure the amount of Beats used for music-aware looping. If zero, it will be autodetected from the length.\nIt is recommended to set this value (either manually or by clicking on a beat number in the preview) to ensure looping works properly."));
diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp
index 6d3d474cee..77b3629b07 100644
--- a/editor/import/resource_importer_wav.cpp
+++ b/editor/import/resource_importer_wav.cpp
@@ -90,7 +90,8 @@ void ResourceImporterWAV::get_import_options(const String &p_path, List<ImportOp
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_mode", PROPERTY_HINT_ENUM, "Detect From WAV,Disabled,Forward,Ping-Pong,Backward", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_begin"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_end"), -1));
- r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "Disabled,RAM (Ima-ADPCM),QOA (Quite OK Audio)"), 0));
+ // Quite OK Audio is lightweight enough and supports virtually every significant AudioStreamWAV feature.
+ r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "PCM (Uncompressed),IMA ADPCM,Quite OK Audio"), 2));
}
Error ResourceImporterWAV::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
@@ -517,16 +518,19 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
Vector<uint8_t> dst_data;
if (compression == 2) {
dst_format = AudioStreamWAV::FORMAT_QOA;
- qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } };
+ qoa_desc desc = {};
uint32_t qoa_len = 0;
desc.samplerate = rate;
desc.samples = frames;
desc.channels = format_channels;
- void *encoded = qoa_encode((short *)pcm_data.ptrw(), &desc, &qoa_len);
- dst_data.resize(qoa_len);
- memcpy(dst_data.ptrw(), encoded, qoa_len);
+ void *encoded = qoa_encode((short *)pcm_data.ptr(), &desc, &qoa_len);
+ if (encoded) {
+ dst_data.resize(qoa_len);
+ memcpy(dst_data.ptrw(), encoded, qoa_len);
+ QOA_FREE(encoded);
+ }
} else {
dst_data = pcm_data;
}
diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp
index dc839b02f6..c60197b96b 100644
--- a/editor/input_event_configuration_dialog.cpp
+++ b/editor/input_event_configuration_dialog.cpp
@@ -720,7 +720,7 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() {
for (int i = 0; i < MOD_MAX; i++) {
String name = mods[i];
mod_checkboxes[i] = memnew(CheckBox);
- mod_checkboxes[i]->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_mod_toggled).bind(i));
+ mod_checkboxes[i]->connect(SceneStringName(toggled), callable_mp(this, &InputEventConfigurationDialog::_mod_toggled).bind(i));
mod_checkboxes[i]->set_text(name);
mod_checkboxes[i]->set_tooltip_text(TTR(mods_tip[i]));
mod_container->add_child(mod_checkboxes[i]);
@@ -729,7 +729,7 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() {
mod_container->add_child(memnew(VSeparator));
autoremap_command_or_control_checkbox = memnew(CheckBox);
- autoremap_command_or_control_checkbox->connect("toggled", callable_mp(this, &InputEventConfigurationDialog::_autoremap_command_or_control_toggled));
+ autoremap_command_or_control_checkbox->connect(SceneStringName(toggled), callable_mp(this, &InputEventConfigurationDialog::_autoremap_command_or_control_toggled));
autoremap_command_or_control_checkbox->set_pressed(false);
autoremap_command_or_control_checkbox->set_text(TTR("Command / Control (auto)"));
autoremap_command_or_control_checkbox->set_tooltip_text(TTR("Automatically remaps between 'Meta' ('Command') and 'Control' depending on current platform."));
diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp
index aa075c80c3..dc07403213 100644
--- a/editor/inspector_dock.cpp
+++ b/editor/inspector_dock.cpp
@@ -190,7 +190,7 @@ void InspectorDock::_menu_option_confirm(int p_option, bool p_confirmed) {
}
int history_id = EditorUndoRedoManager::get_singleton()->get_history_id_for_object(current);
- EditorUndoRedoManager::get_singleton()->clear_history(true, history_id);
+ EditorUndoRedoManager::get_singleton()->clear_history(history_id);
EditorNode::get_singleton()->edit_item(current, inspector);
}
diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp
index 7d580e8de9..cbf8b27b32 100644
--- a/editor/plugins/animation_blend_space_1d_editor.cpp
+++ b/editor/plugins/animation_blend_space_1d_editor.cpp
@@ -713,7 +713,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
top_hb->add_child(memnew(Label(TTR("Sync:"))));
sync = memnew(CheckBox);
top_hb->add_child(sync);
- sync->connect("toggled", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
+ sync->connect(SceneStringName(toggled), callable_mp(this, &AnimationNodeBlendSpace1DEditor::_config_changed));
top_hb->add_child(memnew(VSeparator));
diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp
index 55949df54f..934f26415a 100644
--- a/editor/plugins/animation_blend_space_2d_editor.cpp
+++ b/editor/plugins/animation_blend_space_2d_editor.cpp
@@ -961,7 +961,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() {
top_hb->add_child(memnew(Label(TTR("Sync:"))));
sync = memnew(CheckBox);
top_hb->add_child(sync);
- sync->connect("toggled", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_config_changed));
+ sync->connect(SceneStringName(toggled), callable_mp(this, &AnimationNodeBlendSpace2DEditor::_config_changed));
top_hb->add_child(memnew(VSeparator));
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 660e4647a1..b882112950 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -224,6 +224,69 @@ void AnimationPlayerEditor::_autoplay_pressed() {
}
}
+void AnimationPlayerEditor::_go_to_nearest_keyframe(bool p_backward) {
+ if (_get_current().is_empty()) {
+ return;
+ }
+
+ Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
+
+ double current_time = player->get_current_animation_position();
+ // Offset the time to avoid finding the same keyframe with Animation::track_find_key().
+ double time_offset = MAX(CMP_EPSILON * 2, current_time * CMP_EPSILON * 2);
+ double current_time_offset = current_time + (p_backward ? -time_offset : time_offset);
+
+ float nearest_key_time = p_backward ? 0 : anim->get_length();
+ int track_count = anim->get_track_count();
+ bool bezier_active = track_editor->is_bezier_editor_active();
+
+ Node *root = get_tree()->get_edited_scene_root();
+ EditorSelection *selection = EditorNode::get_singleton()->get_editor_selection();
+
+ Vector<int> selected_tracks;
+ for (int i = 0; i < track_count; ++i) {
+ if (selection->is_selected(root->get_node_or_null(anim->track_get_path(i)))) {
+ selected_tracks.push_back(i);
+ }
+ }
+
+ // Find the nearest keyframe in selection if the scene has selected nodes
+ // or the nearest keyframe in the entire animation otherwise.
+ if (selected_tracks.size() > 0) {
+ for (int track : selected_tracks) {
+ if (bezier_active && anim->track_get_type(track) != Animation::TYPE_BEZIER) {
+ continue;
+ }
+ int key = anim->track_find_key(track, current_time_offset, Animation::FIND_MODE_NEAREST, false, !p_backward);
+ if (key == -1) {
+ continue;
+ }
+ double key_time = anim->track_get_key_time(track, key);
+ if ((p_backward && key_time > nearest_key_time) || (!p_backward && key_time < nearest_key_time)) {
+ nearest_key_time = key_time;
+ }
+ }
+ } else {
+ for (int track = 0; track < track_count; ++track) {
+ if (bezier_active && anim->track_get_type(track) != Animation::TYPE_BEZIER) {
+ continue;
+ }
+ int key = anim->track_find_key(track, current_time_offset, Animation::FIND_MODE_NEAREST, false, !p_backward);
+ if (key == -1) {
+ continue;
+ }
+ double key_time = anim->track_get_key_time(track, key);
+ if ((p_backward && key_time > nearest_key_time) || (!p_backward && key_time < nearest_key_time)) {
+ nearest_key_time = key_time;
+ }
+ }
+ }
+
+ player->seek_internal(nearest_key_time, true, true, true);
+ frame->set_value(nearest_key_time);
+ track_editor->set_anim_pos(nearest_key_time);
+}
+
void AnimationPlayerEditor::_play_pressed() {
String current = _get_current();
@@ -474,17 +537,12 @@ void AnimationPlayerEditor::_select_anim_by_name(const String &p_anim) {
}
float AnimationPlayerEditor::_get_editor_step() const {
- // Returns the effective snapping value depending on snapping modifiers, or 0 if snapping is disabled.
- if (track_editor->is_snap_enabled()) {
- const String current = player->get_assigned_animation();
- const Ref<Animation> anim = player->get_animation(current);
- ERR_FAIL_COND_V(!anim.is_valid(), 0.0);
+ const String current = player->get_assigned_animation();
+ const Ref<Animation> anim = player->get_animation(current);
+ ERR_FAIL_COND_V(anim.is_null(), 0.0);
- // Use more precise snapping when holding Shift
- return Input::get_singleton()->is_key_pressed(Key::SHIFT) ? anim->get_step() * 0.25 : anim->get_step();
- }
-
- return 0.0f;
+ // Use more precise snapping when holding Shift
+ return Input::get_singleton()->is_key_pressed(Key::SHIFT) ? anim->get_step() * 0.25 : anim->get_step();
}
void AnimationPlayerEditor::_animation_name_edited() {
@@ -1290,7 +1348,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_timeline_o
anim = player->get_animation(current);
double pos = CLAMP((double)anim->get_length() * (p_value / frame->get_max()), 0, (double)anim->get_length());
- if (track_editor->is_snap_enabled()) {
+ if (track_editor->is_snap_timeline_enabled()) {
pos = Math::snapped(pos, _get_editor_step());
}
pos = CLAMP(pos, 0, (double)anim->get_length() - CMP_EPSILON2); // Hack: Avoid fposmod with LOOP_LINEAR.
@@ -1408,11 +1466,17 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_timel
}
updating = true;
- frame->set_value(Math::snapped(p_pos, _get_editor_step()));
+ frame->set_value(track_editor->is_snap_timeline_enabled() ? Math::snapped(p_pos, _get_editor_step()) : p_pos);
updating = false;
_seek_value_changed(p_pos, p_timeline_only);
}
+void AnimationPlayerEditor::_animation_update_key_frame() {
+ if (player) {
+ player->advance(0);
+ }
+}
+
void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
String current = _get_current();
@@ -1505,30 +1569,28 @@ void AnimationPlayerEditor::shortcut_input(const Ref<InputEvent> &p_ev) {
ERR_FAIL_COND(p_ev.is_null());
Ref<InputEventKey> k = p_ev;
- if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo() && !k->is_alt_pressed() && !k->is_ctrl_pressed() && !k->is_meta_pressed()) {
- switch (k->get_keycode()) {
- case Key::A: {
- if (!k->is_shift_pressed()) {
- _play_bw_from_pressed();
- } else {
- _play_bw_pressed();
- }
- accept_event();
- } break;
- case Key::S: {
- _stop_pressed();
- accept_event();
- } break;
- case Key::D: {
- if (!k->is_shift_pressed()) {
- _play_from_pressed();
- } else {
- _play_pressed();
- }
- accept_event();
- } break;
- default:
- break;
+ if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo()) {
+ if (ED_IS_SHORTCUT("animation_editor/stop_animation", p_ev)) {
+ _stop_pressed();
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/play_animation", p_ev)) {
+ _play_from_pressed();
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/play_animation_backwards", p_ev)) {
+ _play_bw_from_pressed();
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/play_animation_from_start", p_ev)) {
+ _play_pressed();
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/play_animation_from_end", p_ev)) {
+ _play_bw_pressed();
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/go_to_next_keyframe", p_ev)) {
+ _go_to_nearest_keyframe(false);
+ accept_event();
+ } else if (ED_IS_SHORTCUT("animation_editor/go_to_previous_keyframe", p_ev)) {
+ _go_to_nearest_keyframe(true);
+ accept_event();
}
}
}
@@ -1874,6 +1936,7 @@ bool AnimationPlayerEditor::_validate_tracks(const Ref<Animation> p_anim) {
void AnimationPlayerEditor::_bind_methods() {
// Needed for UndoRedo.
ClassDB::bind_method(D_METHOD("_animation_player_changed"), &AnimationPlayerEditor::_animation_player_changed);
+ ClassDB::bind_method(D_METHOD("_animation_update_key_frame"), &AnimationPlayerEditor::_animation_update_key_frame);
ClassDB::bind_method(D_METHOD("_start_onion_skinning"), &AnimationPlayerEditor::_start_onion_skinning);
ClassDB::bind_method(D_METHOD("_stop_onion_skinning"), &AnimationPlayerEditor::_stop_onion_skinning);
@@ -1902,27 +1965,27 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
play_bw_from = memnew(Button);
play_bw_from->set_theme_type_variation("FlatButton");
- play_bw_from->set_tooltip_text(TTR("Play selected animation backwards from current pos. (A)"));
+ play_bw_from->set_tooltip_text(TTR("Play Animation Backwards"));
hb->add_child(play_bw_from);
play_bw = memnew(Button);
play_bw->set_theme_type_variation("FlatButton");
- play_bw->set_tooltip_text(TTR("Play selected animation backwards from end. (Shift+A)"));
+ play_bw->set_tooltip_text(TTR("Play Animation Backwards from End"));
hb->add_child(play_bw);
stop = memnew(Button);
stop->set_theme_type_variation("FlatButton");
+ stop->set_tooltip_text(TTR("Pause/Stop Animation"));
hb->add_child(stop);
- stop->set_tooltip_text(TTR("Pause/stop animation playback. (S)"));
play = memnew(Button);
play->set_theme_type_variation("FlatButton");
- play->set_tooltip_text(TTR("Play selected animation from start. (Shift+D)"));
+ play->set_tooltip_text(TTR("Play Animation from Start"));
hb->add_child(play);
play_from = memnew(Button);
play_from->set_theme_type_variation("FlatButton");
- play_from->set_tooltip_text(TTR("Play selected animation from current pos. (D)"));
+ play_from->set_tooltip_text(TTR("Play Animation"));
hb->add_child(play_from);
frame = memnew(SpinBox);
@@ -2138,6 +2201,14 @@ void fragment() {
}
)");
RS::get_singleton()->material_set_shader(onion.capture.material->get_rid(), onion.capture.shader->get_rid());
+
+ ED_SHORTCUT("animation_editor/stop_animation", TTR("Pause/Stop Animation"), Key::S);
+ ED_SHORTCUT("animation_editor/play_animation", TTR("Play Animation"), Key::D);
+ ED_SHORTCUT("animation_editor/play_animation_backwards", TTR("Play Animation Backwards"), Key::A);
+ ED_SHORTCUT("animation_editor/play_animation_from_start", TTR("Play Animation from Start"), KeyModifierMask::SHIFT + Key::D);
+ ED_SHORTCUT("animation_editor/play_animation_from_end", TTR("Play Animation Backwards from End"), KeyModifierMask::SHIFT + Key::A);
+ ED_SHORTCUT("animation_editor/go_to_next_keyframe", TTR("Go to Next Keyframe"), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::D);
+ ED_SHORTCUT("animation_editor/go_to_previous_keyframe", TTR("Go to Previous Keyframe"), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::A);
}
AnimationPlayerEditor::~AnimationPlayerEditor() {
@@ -2165,7 +2236,7 @@ void AnimationPlayerEditorPlugin::_property_keyed(const String &p_keyed, const V
return;
}
te->_clear_selection();
- te->insert_value_key(p_keyed, p_value, p_advance);
+ te->insert_value_key(p_keyed, p_advance);
}
void AnimationPlayerEditorPlugin::_transform_key_request(Object *sp, const String &p_sub, const Transform3D &p_key) {
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index 4a3b1f37ab..860d421b91 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -178,6 +178,7 @@ class AnimationPlayerEditor : public VBoxContainer {
void _select_anim_by_name(const String &p_anim);
float _get_editor_step() const;
+ void _go_to_nearest_keyframe(bool p_backward);
void _play_pressed();
void _play_from_pressed();
void _play_bw_pressed();
@@ -216,6 +217,7 @@ class AnimationPlayerEditor : public VBoxContainer {
void _animation_key_editor_seek(float p_pos, bool p_timeline_only = false, bool p_update_position_only = false);
void _animation_key_editor_anim_len_changed(float p_len);
+ void _animation_update_key_frame();
virtual void shortcut_input(const Ref<InputEvent> &p_ev) override;
void _animation_tool_menu(int p_option);
diff --git a/editor/plugins/bone_map_editor_plugin.cpp b/editor/plugins/bone_map_editor_plugin.cpp
index 015dfdbca5..32ff478c33 100644
--- a/editor/plugins/bone_map_editor_plugin.cpp
+++ b/editor/plugins/bone_map_editor_plugin.cpp
@@ -1229,9 +1229,11 @@ void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
picklist.push_back("face");
int head = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, neck);
if (head == -1) {
- search_path = skeleton->get_bone_children(neck);
- if (search_path.size() == 1) {
- head = search_path[0]; // Maybe only one child of the Neck is Head.
+ if (neck != -1) {
+ search_path = skeleton->get_bone_children(neck);
+ if (search_path.size() == 1) {
+ head = search_path[0]; // Maybe only one child of the Neck is Head.
+ }
}
}
if (head == -1) {
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 1afe2ddda7..6e41e98360 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -64,6 +64,7 @@
#include "scene/resources/style_box_texture.h"
#define RULER_WIDTH (15 * EDSCALE)
+#define DRAG_THRESHOLD (8 * EDSCALE)
constexpr real_t SCALE_HANDLE_DISTANCE = 25;
constexpr real_t MOVE_HANDLE_DISTANCE = 25;
@@ -2319,7 +2320,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> m = p_event;
Ref<InputEventKey> k = p_event;
- if (drag_type == DRAG_NONE) {
+ if (drag_type == DRAG_NONE || (drag_type == DRAG_BOX_SELECTION && b.is_valid() && !b->is_pressed())) {
if (b.is_valid() && b->is_pressed() &&
((b->get_button_index() == MouseButton::RIGHT && b->is_alt_pressed() && tool == TOOL_SELECT) ||
(b->get_button_index() == MouseButton::LEFT && tool == TOOL_LIST_SELECT))) {
@@ -2411,47 +2412,58 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
return true;
}
- if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && !panner->is_panning() && (tool == TOOL_SELECT || tool == TOOL_MOVE || tool == TOOL_SCALE || tool == TOOL_ROTATE)) {
- // Single item selection
- Point2 click = transform.affine_inverse().xform(b->get_position());
+ Point2 click;
+ bool can_select = b.is_valid() && b->get_button_index() == MouseButton::LEFT && !panner->is_panning() && (tool == TOOL_SELECT || tool == TOOL_MOVE || tool == TOOL_SCALE || tool == TOOL_ROTATE);
+ if (can_select) {
+ click = transform.affine_inverse().xform(b->get_position());
+ // Allow selecting on release when performed very small box selection (necessary when Shift is pressed, see below).
+ can_select = b->is_pressed() || (drag_type == DRAG_BOX_SELECTION && click.distance_to(drag_from) <= DRAG_THRESHOLD);
+ }
+ if (can_select) {
+ // Single item selection.
Node *scene = EditorNode::get_singleton()->get_edited_scene();
if (!scene) {
return true;
}
- // Find the item to select
+ // Find the item to select.
CanvasItem *ci = nullptr;
Vector<_SelectResult> selection = Vector<_SelectResult>();
- // Retrieve the canvas items
+ // Retrieve the canvas items.
_get_canvas_items_at_pos(click, selection);
if (!selection.is_empty()) {
ci = selection[0].item;
}
- if (!ci) {
- // Start a box selection
+ // Shift also allows forcing box selection when item was clicked.
+ if (!ci || (b->is_shift_pressed() && b->is_pressed())) {
+ // Start a box selection.
if (!b->is_shift_pressed()) {
- // Clear the selection if not additive
+ // Clear the selection if not additive.
editor_selection->clear();
viewport->queue_redraw();
selected_from_canvas = true;
};
- drag_from = click;
- drag_type = DRAG_BOX_SELECTION;
- box_selecting_to = drag_from;
- return true;
+ if (b->is_pressed()) {
+ drag_from = click;
+ drag_type = DRAG_BOX_SELECTION;
+ box_selecting_to = drag_from;
+ return true;
+ }
} else {
bool still_selected = _select_click_on_item(ci, click, b->is_shift_pressed());
- // Start dragging
- if (still_selected && (tool == TOOL_SELECT || tool == TOOL_MOVE)) {
- // Drag the node(s) if requested
+ // Start dragging.
+ if (still_selected && (tool == TOOL_SELECT || tool == TOOL_MOVE) && b->is_pressed()) {
+ // Drag the node(s) if requested.
drag_start_origin = click;
drag_type = DRAG_QUEUED;
+ } else if (!b->is_pressed()) {
+ _reset_drag();
}
- // Select the item
+ // Select the item.
return true;
}
}
@@ -4319,13 +4331,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
Node2D *n2d = Object::cast_to<Node2D>(ci);
if (key_pos && p_location) {
- te->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing);
+ te->insert_node_value_key(n2d, "position", p_on_existing);
}
if (key_rot && p_rotation) {
- te->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing);
+ te->insert_node_value_key(n2d, "rotation", p_on_existing);
}
if (key_scale && p_scale) {
- te->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing);
+ te->insert_node_value_key(n2d, "scale", p_on_existing);
}
if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) {
@@ -4351,13 +4363,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
if (has_chain && ik_chain.size()) {
for (Node2D *&F : ik_chain) {
if (key_pos) {
- te->insert_node_value_key(F, "position", F->get_position(), p_on_existing);
+ te->insert_node_value_key(F, "position", p_on_existing);
}
if (key_rot) {
- te->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing);
+ te->insert_node_value_key(F, "rotation", p_on_existing);
}
if (key_scale) {
- te->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing);
+ te->insert_node_value_key(F, "scale", p_on_existing);
}
}
}
@@ -4367,13 +4379,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
Control *ctrl = Object::cast_to<Control>(ci);
if (key_pos) {
- te->insert_node_value_key(ctrl, "position", ctrl->get_position(), p_on_existing);
+ te->insert_node_value_key(ctrl, "position", p_on_existing);
}
if (key_rot) {
- te->insert_node_value_key(ctrl, "rotation", ctrl->get_rotation(), p_on_existing);
+ te->insert_node_value_key(ctrl, "rotation", p_on_existing);
}
if (key_scale) {
- te->insert_node_value_key(ctrl, "size", ctrl->get_size(), p_on_existing);
+ te->insert_node_value_key(ctrl, "size", p_on_existing);
}
}
}
@@ -5397,7 +5409,7 @@ CanvasItemEditor::CanvasItemEditor() {
smart_snap_button->set_theme_type_variation("FlatButton");
main_menu_hbox->add_child(smart_snap_button);
smart_snap_button->set_toggle_mode(true);
- smart_snap_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_toggle_smart_snap));
+ smart_snap_button->connect(SceneStringName(toggled), callable_mp(this, &CanvasItemEditor::_button_toggle_smart_snap));
smart_snap_button->set_tooltip_text(TTR("Toggle smart snapping."));
smart_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_smart_snap", TTR("Use Smart Snap"), KeyModifierMask::SHIFT | Key::S));
smart_snap_button->set_shortcut_context(this);
@@ -5406,7 +5418,7 @@ CanvasItemEditor::CanvasItemEditor() {
grid_snap_button->set_theme_type_variation("FlatButton");
main_menu_hbox->add_child(grid_snap_button);
grid_snap_button->set_toggle_mode(true);
- grid_snap_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_toggle_grid_snap));
+ grid_snap_button->connect(SceneStringName(toggled), callable_mp(this, &CanvasItemEditor::_button_toggle_grid_snap));
grid_snap_button->set_tooltip_text(TTR("Toggle grid snapping."));
grid_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_grid_snap", TTR("Use Grid Snap"), KeyModifierMask::SHIFT | Key::G));
grid_snap_button->set_shortcut_context(this);
@@ -5499,7 +5511,7 @@ CanvasItemEditor::CanvasItemEditor() {
override_camera_button = memnew(Button);
override_camera_button->set_theme_type_variation("FlatButton");
main_menu_hbox->add_child(override_camera_button);
- override_camera_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_override_camera));
+ override_camera_button->connect(SceneStringName(toggled), callable_mp(this, &CanvasItemEditor::_button_override_camera));
override_camera_button->set_toggle_mode(true);
override_camera_button->set_disabled(true);
_update_override_camera_button(false);
diff --git a/editor/plugins/control_editor_plugin.cpp b/editor/plugins/control_editor_plugin.cpp
index df20395ac5..5c5f236ff3 100644
--- a/editor/plugins/control_editor_plugin.cpp
+++ b/editor/plugins/control_editor_plugin.cpp
@@ -1084,7 +1084,7 @@ ControlEditorToolbar::ControlEditorToolbar() {
anchor_mode_button->set_toggle_mode(true);
anchor_mode_button->set_tooltip_text(TTR("When active, moving Control nodes changes their anchors instead of their offsets."));
add_child(anchor_mode_button);
- anchor_mode_button->connect("toggled", callable_mp(this, &ControlEditorToolbar::_anchor_mode_toggled));
+ anchor_mode_button->connect(SceneStringName(toggled), callable_mp(this, &ControlEditorToolbar::_anchor_mode_toggled));
// Container tools.
containers_button = memnew(ControlEditorPopupButton);
diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp
index 180de700b7..e518cf7815 100644
--- a/editor/plugins/curve_editor_plugin.cpp
+++ b/editor/plugins/curve_editor_plugin.cpp
@@ -1003,7 +1003,7 @@ CurveEditor::CurveEditor() {
snap_button->set_tooltip_text(TTR("Toggle Grid Snap"));
snap_button->set_toggle_mode(true);
toolbar->add_child(snap_button);
- snap_button->connect("toggled", callable_mp(this, &CurveEditor::_set_snap_enabled));
+ snap_button->connect(SceneStringName(toggled), callable_mp(this, &CurveEditor::_set_snap_enabled));
toolbar->add_child(memnew(VSeparator));
diff --git a/editor/plugins/editor_plugin.cpp b/editor/plugins/editor_plugin.cpp
index d9f60e155d..8ce667568f 100644
--- a/editor/plugins/editor_plugin.cpp
+++ b/editor/plugins/editor_plugin.cpp
@@ -40,6 +40,7 @@
#include "editor/editor_translation_parser.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/export/editor_export.h"
+#include "editor/export/editor_export_platform.h"
#include "editor/gui/editor_bottom_panel.h"
#include "editor/gui/editor_title_bar.h"
#include "editor/import/3d/resource_importer_scene.h"
@@ -441,6 +442,16 @@ void EditorPlugin::remove_export_plugin(const Ref<EditorExportPlugin> &p_exporte
EditorExport::get_singleton()->remove_export_plugin(p_exporter);
}
+void EditorPlugin::add_export_platform(const Ref<EditorExportPlatform> &p_platform) {
+ ERR_FAIL_COND(p_platform.is_null());
+ EditorExport::get_singleton()->add_export_platform(p_platform);
+}
+
+void EditorPlugin::remove_export_platform(const Ref<EditorExportPlatform> &p_platform) {
+ ERR_FAIL_COND(p_platform.is_null());
+ EditorExport::get_singleton()->remove_export_platform(p_platform);
+}
+
void EditorPlugin::add_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin) {
ERR_FAIL_COND(!p_gizmo_plugin.is_valid());
Node3DEditor::get_singleton()->add_gizmo_plugin(p_gizmo_plugin);
@@ -608,6 +619,8 @@ void EditorPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove_scene_post_import_plugin", "scene_import_plugin"), &EditorPlugin::remove_scene_post_import_plugin);
ClassDB::bind_method(D_METHOD("add_export_plugin", "plugin"), &EditorPlugin::add_export_plugin);
ClassDB::bind_method(D_METHOD("remove_export_plugin", "plugin"), &EditorPlugin::remove_export_plugin);
+ ClassDB::bind_method(D_METHOD("add_export_platform", "platform"), &EditorPlugin::add_export_platform);
+ ClassDB::bind_method(D_METHOD("remove_export_platform", "platform"), &EditorPlugin::remove_export_platform);
ClassDB::bind_method(D_METHOD("add_node_3d_gizmo_plugin", "plugin"), &EditorPlugin::add_node_3d_gizmo_plugin);
ClassDB::bind_method(D_METHOD("remove_node_3d_gizmo_plugin", "plugin"), &EditorPlugin::remove_node_3d_gizmo_plugin);
ClassDB::bind_method(D_METHOD("add_inspector_plugin", "plugin"), &EditorPlugin::add_inspector_plugin);
diff --git a/editor/plugins/editor_plugin.h b/editor/plugins/editor_plugin.h
index f6c4b35407..2e0771f906 100644
--- a/editor/plugins/editor_plugin.h
+++ b/editor/plugins/editor_plugin.h
@@ -41,6 +41,7 @@ class PopupMenu;
class EditorDebuggerPlugin;
class EditorExport;
class EditorExportPlugin;
+class EditorExportPlatform;
class EditorImportPlugin;
class EditorInspectorPlugin;
class EditorInterface;
@@ -224,6 +225,9 @@ public:
void add_export_plugin(const Ref<EditorExportPlugin> &p_exporter);
void remove_export_plugin(const Ref<EditorExportPlugin> &p_exporter);
+ void add_export_platform(const Ref<EditorExportPlatform> &p_platform);
+ void remove_export_platform(const Ref<EditorExportPlatform> &p_platform);
+
void add_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin);
void remove_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin);
diff --git a/editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp
index ae24b4250e..c277ec8cd3 100644
--- a/editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp
+++ b/editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp
@@ -293,9 +293,15 @@ Joint3DGizmoPlugin::Joint3DGizmoPlugin() {
void Joint3DGizmoPlugin::incremental_update_gizmos() {
if (!current_gizmos.is_empty()) {
- update_idx++;
- update_idx = update_idx % current_gizmos.size();
- redraw(current_gizmos.get(update_idx));
+ HashSet<EditorNode3DGizmo *>::Iterator E = current_gizmos.find(last_drawn);
+ if (E) {
+ ++E;
+ }
+ if (!E) {
+ E = current_gizmos.begin();
+ }
+ redraw(*E);
+ last_drawn = *E;
}
}
diff --git a/editor/plugins/gizmos/joint_3d_gizmo_plugin.h b/editor/plugins/gizmos/joint_3d_gizmo_plugin.h
index 79fe40d1b2..25b08d71c6 100644
--- a/editor/plugins/gizmos/joint_3d_gizmo_plugin.h
+++ b/editor/plugins/gizmos/joint_3d_gizmo_plugin.h
@@ -37,7 +37,7 @@ class Joint3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(Joint3DGizmoPlugin, EditorNode3DGizmoPlugin);
Timer *update_timer = nullptr;
- uint64_t update_idx = 0;
+ EditorNode3DGizmo *last_drawn = nullptr;
void incremental_update_gizmos();
diff --git a/editor/plugins/gradient_editor_plugin.cpp b/editor/plugins/gradient_editor_plugin.cpp
index 8bf5dad97f..1300394ca3 100644
--- a/editor/plugins/gradient_editor_plugin.cpp
+++ b/editor/plugins/gradient_editor_plugin.cpp
@@ -632,7 +632,7 @@ GradientEditor::GradientEditor() {
snap_button->set_tooltip_text(TTR("Toggle Grid Snap"));
snap_button->set_toggle_mode(true);
toolbar->add_child(snap_button);
- snap_button->connect("toggled", callable_mp(this, &GradientEditor::_set_snap_enabled));
+ snap_button->connect(SceneStringName(toggled), callable_mp(this, &GradientEditor::_set_snap_enabled));
snap_count_edit = memnew(EditorSpinSlider);
snap_count_edit->set_min(2);
diff --git a/editor/plugins/gradient_texture_2d_editor_plugin.cpp b/editor/plugins/gradient_texture_2d_editor_plugin.cpp
index 7e22e1209c..5bf1422780 100644
--- a/editor/plugins/gradient_texture_2d_editor_plugin.cpp
+++ b/editor/plugins/gradient_texture_2d_editor_plugin.cpp
@@ -290,7 +290,7 @@ GradientTexture2DEditor::GradientTexture2DEditor() {
snap_button->set_tooltip_text(TTR("Toggle Grid Snap"));
snap_button->set_toggle_mode(true);
toolbar->add_child(snap_button);
- snap_button->connect("toggled", callable_mp(this, &GradientTexture2DEditor::_set_snap_enabled));
+ snap_button->connect(SceneStringName(toggled), callable_mp(this, &GradientTexture2DEditor::_set_snap_enabled));
snap_count_edit = memnew(EditorSpinSlider);
snap_count_edit->set_min(2);
diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp
index 67d5e44ce5..1b2cd441ad 100644
--- a/editor/plugins/node_3d_editor_gizmos.cpp
+++ b/editor/plugins/node_3d_editor_gizmos.cpp
@@ -1060,7 +1060,7 @@ Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::get_gizmo(Node3D *p_spatial) {
ref->set_node_3d(p_spatial);
ref->set_hidden(current_state == HIDDEN);
- current_gizmos.push_back(ref.ptr());
+ current_gizmos.insert(ref.ptr());
return ref;
}
diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h
index c4b275032a..1916bc2058 100644
--- a/editor/plugins/node_3d_editor_gizmos.h
+++ b/editor/plugins/node_3d_editor_gizmos.h
@@ -157,7 +157,7 @@ public:
protected:
int current_state;
- List<EditorNode3DGizmo *> current_gizmos;
+ HashSet<EditorNode3DGizmo *> current_gizmos;
HashMap<String, Vector<Ref<StandardMaterial3D>>> materials;
static void _bind_methods();
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index d911e745db..8b0f4a64a7 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -770,7 +770,7 @@ void Node3DEditorViewport::_select_clicked(bool p_allow_locked) {
}
}
- if (p_allow_locked || !_is_node_locked(selected)) {
+ if (p_allow_locked || (selected != nullptr && !_is_node_locked(selected))) {
if (clicked_wants_append) {
if (editor_selection->is_selected(selected)) {
editor_selection->remove_node(selected);
@@ -1692,6 +1692,10 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (b.is_valid()) {
emit_signal(SNAME("clicked"), this);
+ ViewportNavMouseButton orbit_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/orbit_mouse_button").operator int();
+ ViewportNavMouseButton pan_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/pan_mouse_button").operator int();
+ ViewportNavMouseButton zoom_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/zoom_mouse_button").operator int();
+
const real_t zoom_factor = 1 + (ZOOM_FREELOOK_MULTIPLIER - 1) * b->get_factor();
switch (b->get_button_index()) {
case MouseButton::WHEEL_UP: {
@@ -1709,8 +1713,6 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
} break;
case MouseButton::RIGHT: {
- NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
-
if (b->is_pressed() && _edit.gizmo.is_valid()) {
//restore
_edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, true);
@@ -1718,11 +1720,15 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
if (_edit.mode == TRANSFORM_NONE && b->is_pressed()) {
- if (b->is_alt_pressed()) {
- if (nav_scheme == NAVIGATION_MAYA) {
- break;
- }
+ if (orbit_mouse_preference == NAVIGATION_RIGHT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2")) {
+ break;
+ } else if (pan_mouse_preference == NAVIGATION_RIGHT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2")) {
+ break;
+ } else if (zoom_mouse_preference == NAVIGATION_RIGHT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2")) {
+ break;
+ }
+ if (b->is_alt_pressed()) {
_list_select(b);
return;
}
@@ -1753,6 +1759,14 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
} break;
case MouseButton::MIDDLE: {
if (b->is_pressed() && _edit.mode != TRANSFORM_NONE) {
+ if (orbit_mouse_preference == NAVIGATION_MIDDLE_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2")) {
+ break;
+ } else if (pan_mouse_preference == NAVIGATION_MIDDLE_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2")) {
+ break;
+ } else if (zoom_mouse_preference == NAVIGATION_MIDDLE_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2")) {
+ break;
+ }
+
switch (_edit.plane) {
case TRANSFORM_VIEW: {
_edit.plane = TRANSFORM_X_AXIS;
@@ -1791,8 +1805,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
commit_transform();
break; // just commit the edit, stop processing the event so we don't deselect the object
}
- NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
- if ((nav_scheme == NAVIGATION_MAYA || nav_scheme == NAVIGATION_MODO) && b->is_alt_pressed()) {
+ if (orbit_mouse_preference == NAVIGATION_LEFT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2")) {
+ break;
+ } else if (pan_mouse_preference == NAVIGATION_LEFT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2")) {
+ break;
+ } else if (zoom_mouse_preference == NAVIGATION_LEFT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2")) {
break;
}
@@ -1811,7 +1828,9 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
{
int idx = view_menu->get_popup()->get_item_index(VIEW_GIZMOS);
+ int idx2 = view_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO);
can_select_gizmos = can_select_gizmos && view_menu->get_popup()->is_item_checked(idx);
+ transform_gizmo_visible = view_menu->get_popup()->is_item_checked(idx2);
}
// Gizmo handles
@@ -1988,6 +2007,24 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
}
+ ViewportNavMouseButton orbit_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/orbit_mouse_button").operator int();
+ ViewportNavMouseButton pan_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/pan_mouse_button").operator int();
+ ViewportNavMouseButton zoom_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/zoom_mouse_button").operator int();
+ bool orbit_mod_pressed = _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2");
+ bool pan_mod_pressed = _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2");
+ bool zoom_mod_pressed = _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2");
+ int orbit_mod_input_count = _get_shortcut_input_count("spatial_editor/viewport_orbit_modifier_1") + _get_shortcut_input_count("spatial_editor/viewport_orbit_modifier_2");
+ int pan_mod_input_count = _get_shortcut_input_count("spatial_editor/viewport_pan_modifier_1") + _get_shortcut_input_count("spatial_editor/viewport_pan_modifier_2");
+ int zoom_mod_input_count = _get_shortcut_input_count("spatial_editor/viewport_zoom_modifier_1") + _get_shortcut_input_count("spatial_editor/viewport_zoom_modifier_2");
+ bool orbit_not_empty = !_is_shortcut_empty("spatial_editor/viewport_zoom_modifier_1") || !_is_shortcut_empty("spatial_editor/viewport_zoom_modifier_2");
+ bool pan_not_empty = !_is_shortcut_empty("spatial_editor/viewport_pan_modifier_1") || !_is_shortcut_empty("spatial_editor/viewport_pan_modifier_2");
+ bool zoom_not_empty = !_is_shortcut_empty("spatial_editor/viewport_orbit_modifier_1") || !_is_shortcut_empty("spatial_editor/viewport_orbit_modifier_2");
+ Vector<ShortcutCheckSet> shortcut_check_sets;
+ shortcut_check_sets.push_back(ShortcutCheckSet(orbit_mod_pressed, orbit_not_empty, orbit_mod_input_count, orbit_mouse_preference, NAVIGATION_ORBIT));
+ shortcut_check_sets.push_back(ShortcutCheckSet(pan_mod_pressed, pan_not_empty, pan_mod_input_count, pan_mouse_preference, NAVIGATION_PAN));
+ shortcut_check_sets.push_back(ShortcutCheckSet(zoom_mod_pressed, zoom_not_empty, zoom_mod_input_count, zoom_mouse_preference, NAVIGATION_ZOOM));
+ shortcut_check_sets.sort_custom<ShortcutCheckSetComparator>();
+
Ref<InputEventMouseMotion> m = p_event;
// Instant transforms process mouse motion in input() to handle wrapping.
@@ -2032,7 +2069,6 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
_transform_gizmo_select(_edit.mouse_pos, true);
}
- NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
NavigationMode nav_mode = NAVIGATION_NONE;
if (_edit.gizmo.is_valid()) {
@@ -2042,14 +2078,9 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
set_message(n + ": " + String(v));
} else if (m->get_button_mask().has_flag(MouseButtonMask::LEFT)) {
- if (nav_scheme == NAVIGATION_MAYA && m->is_alt_pressed()) {
- nav_mode = NAVIGATION_ORBIT;
- } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed() && m->is_shift_pressed()) {
- nav_mode = NAVIGATION_PAN;
- } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed() && m->is_command_or_control_pressed()) {
- nav_mode = NAVIGATION_ZOOM;
- } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed()) {
- nav_mode = NAVIGATION_ORBIT;
+ NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_LEFT_MOUSE, shortcut_check_sets, false);
+ if (change_nav_from_shortcut != NAVIGATION_NONE) {
+ nav_mode = change_nav_from_shortcut;
} else {
const bool movement_threshold_passed = _edit.original_mouse_pos.distance_to(_edit.mouse_pos) > 8 * EDSCALE;
@@ -2080,8 +2111,9 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
update_transform(_get_key_modifier(m) == Key::SHIFT);
}
} else if (m->get_button_mask().has_flag(MouseButtonMask::RIGHT) || freelook_active) {
- if (nav_scheme == NAVIGATION_MAYA && m->is_alt_pressed()) {
- nav_mode = NAVIGATION_ZOOM;
+ NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_RIGHT_MOUSE, shortcut_check_sets, false);
+ if (m->get_button_mask().has_flag(MouseButtonMask::RIGHT) && change_nav_from_shortcut != NAVIGATION_NONE) {
+ nav_mode = change_nav_from_shortcut;
} else if (freelook_active) {
nav_mode = NAVIGATION_LOOK;
} else if (orthogonal) {
@@ -2089,34 +2121,16 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
} else if (m->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) {
- const Key mod = _get_key_modifier(m);
- if (nav_scheme == NAVIGATION_GODOT) {
- if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) {
- nav_mode = NAVIGATION_PAN;
- } else if (mod == _get_key_modifier_setting("editors/3d/navigation/zoom_modifier")) {
- nav_mode = NAVIGATION_ZOOM;
- } else if (mod == Key::ALT || mod == _get_key_modifier_setting("editors/3d/navigation/orbit_modifier")) {
- // Always allow Alt as a modifier to better support graphic tablets.
- nav_mode = NAVIGATION_ORBIT;
- }
- } else if (nav_scheme == NAVIGATION_MAYA) {
- if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) {
- nav_mode = NAVIGATION_PAN;
- }
+ NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_MIDDLE_MOUSE, shortcut_check_sets, false);
+ if (change_nav_from_shortcut != NAVIGATION_NONE) {
+ nav_mode = change_nav_from_shortcut;
}
+
} else if (EDITOR_GET("editors/3d/navigation/emulate_3_button_mouse")) {
// Handle trackpad (no external mouse) use case
- const Key mod = _get_key_modifier(m);
-
- if (mod != Key::NONE) {
- if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) {
- nav_mode = NAVIGATION_PAN;
- } else if (mod == _get_key_modifier_setting("editors/3d/navigation/zoom_modifier")) {
- nav_mode = NAVIGATION_ZOOM;
- } else if (mod == Key::ALT || mod == _get_key_modifier_setting("editors/3d/navigation/orbit_modifier")) {
- // Always allow Alt as a modifier to better support graphic tablets.
- nav_mode = NAVIGATION_ORBIT;
- }
+ NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_LEFT_MOUSE, shortcut_check_sets, true);
+ if (change_nav_from_shortcut != NAVIGATION_NONE) {
+ nav_mode = change_nav_from_shortcut;
}
}
@@ -2157,25 +2171,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
Ref<InputEventPanGesture> pan_gesture = p_event;
if (pan_gesture.is_valid()) {
- NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
NavigationMode nav_mode = NAVIGATION_NONE;
- if (nav_scheme == NAVIGATION_GODOT) {
- const Key mod = _get_key_modifier(pan_gesture);
-
- if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) {
- nav_mode = NAVIGATION_PAN;
- } else if (mod == _get_key_modifier_setting("editors/3d/navigation/zoom_modifier")) {
- nav_mode = NAVIGATION_ZOOM;
- } else if (mod == Key::ALT || mod == _get_key_modifier_setting("editors/3d/navigation/orbit_modifier")) {
- // Always allow Alt as a modifier to better support graphic tablets.
- nav_mode = NAVIGATION_ORBIT;
- }
-
- } else if (nav_scheme == NAVIGATION_MAYA) {
- if (pan_gesture->is_alt_pressed()) {
- nav_mode = NAVIGATION_PAN;
- }
+ NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_LEFT_MOUSE, shortcut_check_sets, true);
+ if (change_nav_from_shortcut != NAVIGATION_NONE) {
+ nav_mode = change_nav_from_shortcut;
}
switch (nav_mode) {
@@ -2445,6 +2445,32 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
}
+int Node3DEditorViewport::_get_shortcut_input_count(const String &p_name) {
+ Ref<Shortcut> check_shortcut = ED_GET_SHORTCUT(p_name);
+
+ ERR_FAIL_COND_V_MSG(check_shortcut.is_null(), 0, "The Shortcut was null, possible name mismatch.");
+
+ return check_shortcut->get_events().size();
+}
+
+Node3DEditorViewport::NavigationMode Node3DEditorViewport::_get_nav_mode_from_shortcut_check(ViewportNavMouseButton p_mouse_button, Vector<ShortcutCheckSet> p_shortcut_check_sets, bool p_use_not_empty) {
+ if (p_use_not_empty) {
+ for (const ShortcutCheckSet &shortcut_check_set : p_shortcut_check_sets) {
+ if (shortcut_check_set.mod_pressed && shortcut_check_set.shortcut_not_empty) {
+ return shortcut_check_set.result_nav_mode;
+ }
+ }
+ } else {
+ for (const ShortcutCheckSet &shortcut_check_set : p_shortcut_check_sets) {
+ if (shortcut_check_set.mouse_preference == p_mouse_button && shortcut_check_set.mod_pressed) {
+ return shortcut_check_set.result_nav_mode;
+ }
+ }
+ }
+
+ return NAVIGATION_NONE;
+}
+
void Node3DEditorViewport::_nav_pan(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
const NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
@@ -2644,6 +2670,18 @@ void Node3DEditorViewport::scale_freelook_speed(real_t scale) {
surface->queue_redraw();
}
+bool Node3DEditorViewport::_is_nav_modifier_pressed(const String &p_name) {
+ return _is_shortcut_empty(p_name) || Input::get_singleton()->is_action_pressed(p_name);
+}
+
+bool Node3DEditorViewport::_is_shortcut_empty(const String &p_name) {
+ Ref<Shortcut> check_shortcut = ED_GET_SHORTCUT(p_name);
+
+ ERR_FAIL_COND_V_MSG(check_shortcut.is_null(), true, "The Shortcut was null, possible name mismatch.");
+
+ return check_shortcut->get_events().is_empty();
+}
+
Point2 Node3DEditorViewport::_get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const {
Point2 relative;
if (bool(EDITOR_GET("editors/3d/navigation/warped_mouse_panning"))) {
@@ -3539,6 +3577,15 @@ void Node3DEditorViewport::_menu_option(int p_option) {
view_menu->get_popup()->set_item_checked(idx, current);
} break;
+ case VIEW_TRANSFORM_GIZMO: {
+ int idx = view_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO);
+ bool current = view_menu->get_popup()->is_item_checked(idx);
+ current = !current;
+ transform_gizmo_visible = current;
+
+ spatial_editor->update_transform_gizmo();
+ view_menu->get_popup()->set_item_checked(idx, current);
+ } break;
case VIEW_HALF_RESOLUTION: {
int idx = view_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION);
bool current = view_menu->get_popup()->is_item_checked(idx);
@@ -3682,10 +3729,10 @@ void Node3DEditorViewport::_set_auto_orthogonal() {
}
void Node3DEditorViewport::_preview_exited_scene() {
- preview_camera->disconnect("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
+ preview_camera->disconnect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
preview_camera->set_pressed(false);
_toggle_camera_preview(false);
- preview_camera->connect("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
+ preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
view_menu->show();
}
@@ -3903,7 +3950,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
return;
}
- bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant;
+ bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible;
for (int i = 0; i < 3; i++) {
Transform3D axis_angle;
if (xform.basis.get_column(i).normalized().dot(xform.basis.get_column((i + 1) % 3).normalized()) < 1.0) {
@@ -3934,7 +3981,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
xform.orthonormalize();
xform.basis.scale(scale);
RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], xform);
- RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE));
+ RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && transform_gizmo_visible && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE));
}
void Node3DEditorViewport::set_state(const Dictionary &p_state) {
@@ -4011,6 +4058,14 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) {
_menu_option(VIEW_GIZMOS);
}
}
+ if (p_state.has("transform_gizmo")) {
+ bool transform_gizmo = p_state["transform_gizmo"];
+
+ int idx = view_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO);
+ if (view_menu->get_popup()->is_item_checked(idx) != transform_gizmo) {
+ _menu_option(VIEW_TRANSFORM_GIZMO);
+ }
+ }
if (p_state.has("grid")) {
bool grid = p_state["grid"];
@@ -4049,8 +4104,8 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) {
view_menu->get_popup()->set_item_checked(idx, previewing_cinema);
}
- if (preview_camera->is_connected("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview))) {
- preview_camera->disconnect("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
+ if (preview_camera->is_connected(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview))) {
+ preview_camera->disconnect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
}
if (p_state.has("previewing")) {
Node *pv = EditorNode::get_singleton()->get_edited_scene()->get_node(p_state["previewing"]);
@@ -4063,7 +4118,7 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) {
preview_camera->show();
}
}
- preview_camera->connect("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
+ preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
}
Dictionary Node3DEditorViewport::get_state() const {
@@ -4097,6 +4152,7 @@ Dictionary Node3DEditorViewport::get_state() const {
d["listener"] = viewport->is_audio_listener_3d();
d["doppler"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER));
d["gizmos"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_GIZMOS));
+ d["transform_gizmo"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO));
d["grid"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_GRID));
d["information"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_INFORMATION));
d["frame_time"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_FRAME_TIME));
@@ -5330,6 +5386,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
view_menu->get_popup()->add_separator();
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_environment", TTR("View Environment")), VIEW_ENVIRONMENT);
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_gizmos", TTR("View Gizmos")), VIEW_GIZMOS);
+ view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_transform_gizmo", TTR("View Transform Gizmo")), VIEW_TRANSFORM_GIZMO);
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid_lines", TTR("View Grid")), VIEW_GRID);
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_information", TTR("View Information")), VIEW_INFORMATION);
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_fps", TTR("View Frame Time")), VIEW_FRAME_TIME);
@@ -5340,6 +5397,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_audio_listener", TTR("Audio Listener")), VIEW_AUDIO_LISTENER);
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_audio_doppler", TTR("Enable Doppler")), VIEW_AUDIO_DOPPLER);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_GIZMOS), true);
+ view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO), true);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_GRID), true);
view_menu->get_popup()->add_separator();
@@ -5374,6 +5432,14 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
view_menu->get_popup()->set_item_tooltip(shadeless_idx, unsupported_tooltip);
}
+ // Registering with Key::NONE intentionally creates an empty Array.
+ register_shortcut_action("spatial_editor/viewport_orbit_modifier_1", TTR("Viewport Orbit Modifier 1"), Key::NONE);
+ register_shortcut_action("spatial_editor/viewport_orbit_modifier_2", TTR("Viewport Orbit Modifier 2"), Key::NONE);
+ register_shortcut_action("spatial_editor/viewport_pan_modifier_1", TTR("Viewport Pan Modifier 1"), Key::SHIFT);
+ register_shortcut_action("spatial_editor/viewport_pan_modifier_2", TTR("Viewport Pan Modifier 2"), Key::NONE);
+ register_shortcut_action("spatial_editor/viewport_zoom_modifier_1", TTR("Viewport Zoom Modifier 1"), Key::SHIFT);
+ register_shortcut_action("spatial_editor/viewport_zoom_modifier_2", TTR("Viewport Zoom Modifier 2"), Key::CTRL);
+
register_shortcut_action("spatial_editor/freelook_left", TTR("Freelook Left"), Key::A, true);
register_shortcut_action("spatial_editor/freelook_right", TTR("Freelook Right"), Key::D, true);
register_shortcut_action("spatial_editor/freelook_forward", TTR("Freelook Forward"), Key::W, true);
@@ -5400,7 +5466,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
vbox->add_child(preview_camera);
preview_camera->set_h_size_flags(0);
preview_camera->hide();
- preview_camera->connect("toggled", callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
+ preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
previewing = nullptr;
gizmo_scale = 1.0;
@@ -8615,7 +8681,7 @@ Node3DEditor::Node3DEditor() {
main_menu_hbox->add_child(tool_option_button[TOOL_OPT_LOCAL_COORDS]);
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_toggle_mode(true);
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_theme_type_variation("FlatButton");
- tool_option_button[TOOL_OPT_LOCAL_COORDS]->connect("toggled", callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_LOCAL_COORDS));
+ tool_option_button[TOOL_OPT_LOCAL_COORDS]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_LOCAL_COORDS));
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut(ED_SHORTCUT("spatial_editor/local_coords", TTR("Use Local Space"), Key::T));
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut_context(this);
@@ -8623,7 +8689,7 @@ Node3DEditor::Node3DEditor() {
main_menu_hbox->add_child(tool_option_button[TOOL_OPT_USE_SNAP]);
tool_option_button[TOOL_OPT_USE_SNAP]->set_toggle_mode(true);
tool_option_button[TOOL_OPT_USE_SNAP]->set_theme_type_variation("FlatButton");
- tool_option_button[TOOL_OPT_USE_SNAP]->connect("toggled", callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_USE_SNAP));
+ tool_option_button[TOOL_OPT_USE_SNAP]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_USE_SNAP));
tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut(ED_SHORTCUT("spatial_editor/snap", TTR("Use Snap"), Key::Y));
tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut_context(this);
@@ -8634,7 +8700,7 @@ Node3DEditor::Node3DEditor() {
tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_toggle_mode(true);
tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_theme_type_variation("FlatButton");
tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_disabled(true);
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->connect("toggled", callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_OVERRIDE_CAMERA));
+ tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_OVERRIDE_CAMERA));
_update_camera_override_button(false);
main_menu_hbox->add_child(memnew(VSeparator));
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index 9e7d46c5e8..2cfe784ca6 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -125,6 +125,7 @@ class Node3DEditorViewport : public Control {
VIEW_AUDIO_LISTENER,
VIEW_AUDIO_DOPPLER,
VIEW_GIZMOS,
+ VIEW_TRANSFORM_GIZMO,
VIEW_GRID,
VIEW_INFORMATION,
VIEW_FRAME_TIME,
@@ -192,6 +193,7 @@ public:
NAVIGATION_GODOT,
NAVIGATION_MAYA,
NAVIGATION_MODO,
+ NAVIGATION_CUSTOM,
};
enum FreelookNavigationScheme {
@@ -200,6 +202,12 @@ public:
FREELOOK_FULLY_AXIS_LOCKED,
};
+ enum ViewportNavMouseButton {
+ NAVIGATION_LEFT_MOUSE,
+ NAVIGATION_MIDDLE_MOUSE,
+ NAVIGATION_RIGHT_MOUSE,
+ };
+
private:
double cpu_time_history[FRAME_TIME_HISTORY];
int cpu_time_history_index;
@@ -236,6 +244,7 @@ private:
bool orthogonal;
bool auto_orthogonal;
bool lock_rotation;
+ bool transform_gizmo_visible = true;
real_t gizmo_scale;
bool freelook_active;
@@ -294,6 +303,10 @@ private:
void _nav_orbit(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
void _nav_look(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
+ bool _is_shortcut_empty(const String &p_name);
+ bool _is_nav_modifier_pressed(const String &p_name);
+ int _get_shortcut_input_count(const String &p_name);
+
float get_znear() const;
float get_zfar() const;
float get_fov() const;
@@ -390,6 +403,28 @@ private:
void reset_fov();
void scale_cursor_distance(real_t scale);
+ struct ShortcutCheckSet {
+ bool mod_pressed = false;
+ bool shortcut_not_empty = true;
+ int input_count = 0;
+ ViewportNavMouseButton mouse_preference = NAVIGATION_LEFT_MOUSE;
+ NavigationMode result_nav_mode = NAVIGATION_NONE;
+
+ ShortcutCheckSet() {}
+
+ ShortcutCheckSet(bool p_mod_pressed, bool p_shortcut_not_empty, int p_input_count, const ViewportNavMouseButton &p_mouse_preference, const NavigationMode &p_result_nav_mode) :
+ mod_pressed(p_mod_pressed), shortcut_not_empty(p_shortcut_not_empty), input_count(p_input_count), mouse_preference(p_mouse_preference), result_nav_mode(p_result_nav_mode) {
+ }
+ };
+
+ struct ShortcutCheckSetComparator {
+ _FORCE_INLINE_ bool operator()(const ShortcutCheckSet &A, const ShortcutCheckSet &B) const {
+ return A.input_count > B.input_count;
+ }
+ };
+
+ NavigationMode _get_nav_mode_from_shortcut_check(ViewportNavMouseButton p_mouse_button, Vector<ShortcutCheckSet> p_shortcut_check_sets, bool p_use_not_empty);
+
void set_freelook_active(bool active_now);
void scale_freelook_speed(real_t scale);
diff --git a/editor/plugins/particle_process_material_editor_plugin.cpp b/editor/plugins/particle_process_material_editor_plugin.cpp
index 79c9c69584..67c9403aaf 100644
--- a/editor/plugins/particle_process_material_editor_plugin.cpp
+++ b/editor/plugins/particle_process_material_editor_plugin.cpp
@@ -438,7 +438,7 @@ ParticleProcessMaterialMinMaxPropertyEditor::ParticleProcessMaterialMinMaxProper
toggle_mode_button->set_toggle_mode(true);
toggle_mode_button->set_tooltip_text(TTR("Toggle between minimum/maximum and base value/spread modes."));
hb->add_child(toggle_mode_button);
- toggle_mode_button->connect(SNAME("toggled"), callable_mp(this, &ParticleProcessMaterialMinMaxPropertyEditor::_toggle_mode));
+ toggle_mode_button->connect(SceneStringName(toggled), callable_mp(this, &ParticleProcessMaterialMinMaxPropertyEditor::_toggle_mode));
set_bottom_editor(content_vb);
}
diff --git a/editor/plugins/physical_bone_3d_editor_plugin.cpp b/editor/plugins/physical_bone_3d_editor_plugin.cpp
index d7e9701452..c858fa8606 100644
--- a/editor/plugins/physical_bone_3d_editor_plugin.cpp
+++ b/editor/plugins/physical_bone_3d_editor_plugin.cpp
@@ -62,7 +62,7 @@ PhysicalBone3DEditor::PhysicalBone3DEditor() {
// when the editor theme updates.
button_transform_joint->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("PhysicalBone3D"), EditorStringName(EditorIcons)));
button_transform_joint->set_toggle_mode(true);
- button_transform_joint->connect("toggled", callable_mp(this, &PhysicalBone3DEditor::_on_toggle_button_transform_joint));
+ button_transform_joint->connect(SceneStringName(toggled), callable_mp(this, &PhysicalBone3DEditor::_on_toggle_button_transform_joint));
hide();
}
diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp
index dc99b07067..b8a309bc60 100644
--- a/editor/plugins/polygon_2d_editor_plugin.cpp
+++ b/editor/plugins/polygon_2d_editor_plugin.cpp
@@ -181,7 +181,7 @@ void Polygon2DEditor::_sync_bones() {
}
if (weights.size() == 0) { //create them
- weights.resize(node->get_polygon().size());
+ weights.resize(wc);
float *w = weights.ptrw();
for (int j = 0; j < wc; j++) {
w[j] = 0.0;
@@ -850,8 +850,8 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) {
if (mm.is_valid()) {
if (uv_drag) {
Vector2 uv_drag_to = mm->get_position();
- uv_drag_to = snap_point(uv_drag_to); // FIXME: Only works correctly with 'UV_MODE_EDIT_POINT', it's imprecise with the rest.
- Vector2 drag = mtx.affine_inverse().xform(uv_drag_to) - mtx.affine_inverse().xform(uv_drag_from);
+ uv_drag_to = snap_point(uv_drag_to);
+ Vector2 drag = mtx.affine_inverse().basis_xform(uv_drag_to - uv_drag_from);
switch (uv_move_current) {
case UV_MODE_CREATE: {
@@ -1166,12 +1166,8 @@ void Polygon2DEditor::_uv_draw() {
poly_line_color.a *= 0.25;
}
Color polygon_line_color = Color(0.5, 0.5, 0.9);
- Vector<Color> polygon_fill_color;
- {
- Color pf = polygon_line_color;
- pf.a *= 0.5;
- polygon_fill_color.push_back(pf);
- }
+ Color polygon_fill_color = polygon_line_color;
+ polygon_fill_color.a *= 0.5;
Color prev_color = Color(0.5, 0.5, 0.5);
int uv_draw_max = uvs.size();
@@ -1216,7 +1212,7 @@ void Polygon2DEditor::_uv_draw() {
uv_edit_draw->draw_line(mtx.xform(uvs[idx]), mtx.xform(uvs[idx_next]), polygon_line_color, Math::round(EDSCALE));
}
if (points.size() >= 3) {
- uv_edit_draw->draw_polygon(polypoints, polygon_fill_color);
+ uv_edit_draw->draw_colored_polygon(polypoints, polygon_fill_color);
}
}
@@ -1308,8 +1304,8 @@ void Polygon2DEditor::_bind_methods() {
Vector2 Polygon2DEditor::snap_point(Vector2 p_target) const {
if (use_snap) {
- p_target.x = Math::snap_scalar(snap_offset.x * uv_draw_zoom - uv_draw_ofs.x, snap_step.x * uv_draw_zoom, p_target.x);
- p_target.y = Math::snap_scalar(snap_offset.y * uv_draw_zoom - uv_draw_ofs.y, snap_step.y * uv_draw_zoom, p_target.y);
+ p_target.x = Math::snap_scalar((snap_offset.x - uv_draw_ofs.x) * uv_draw_zoom, snap_step.x * uv_draw_zoom, p_target.x);
+ p_target.y = Math::snap_scalar((snap_offset.y - uv_draw_ofs.y) * uv_draw_zoom, snap_step.y * uv_draw_zoom, p_target.y);
}
return p_target;
@@ -1472,7 +1468,7 @@ Polygon2DEditor::Polygon2DEditor() {
b_snap_enable->set_toggle_mode(true);
b_snap_enable->set_pressed(use_snap);
b_snap_enable->set_tooltip_text(TTR("Enable Snap"));
- b_snap_enable->connect("toggled", callable_mp(this, &Polygon2DEditor::_set_use_snap));
+ b_snap_enable->connect(SceneStringName(toggled), callable_mp(this, &Polygon2DEditor::_set_use_snap));
b_snap_grid = memnew(Button);
b_snap_grid->set_theme_type_variation("FlatButton");
@@ -1482,11 +1478,11 @@ Polygon2DEditor::Polygon2DEditor() {
b_snap_grid->set_toggle_mode(true);
b_snap_grid->set_pressed(snap_show_grid);
b_snap_grid->set_tooltip_text(TTR("Show Grid"));
- b_snap_grid->connect("toggled", callable_mp(this, &Polygon2DEditor::_set_show_grid));
+ b_snap_grid->connect(SceneStringName(toggled), callable_mp(this, &Polygon2DEditor::_set_show_grid));
grid_settings = memnew(AcceptDialog);
grid_settings->set_title(TTR("Configure Grid:"));
- add_child(grid_settings);
+ uv_edit->add_child(grid_settings);
VBoxContainer *grid_settings_vb = memnew(VBoxContainer);
grid_settings->add_child(grid_settings_vb);
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 213af652ca..d7de5a7223 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -493,7 +493,7 @@ void ScriptEditor::_goto_script_line(Ref<RefCounted> p_script, int p_line) {
if (ScriptTextEditor *script_text_editor = Object::cast_to<ScriptTextEditor>(current)) {
script_text_editor->goto_line_centered(p_line);
} else if (current) {
- current->goto_line(p_line, true);
+ current->goto_line(p_line);
}
_save_history();
@@ -578,8 +578,8 @@ void ScriptEditor::_clear_breakpoints() {
script_editor_cache->get_sections(&cached_editors);
for (const String &E : cached_editors) {
Array breakpoints = _get_cached_breakpoints_for_script(E);
- for (int i = 0; i < breakpoints.size(); i++) {
- EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, false);
+ for (int breakpoint : breakpoints) {
+ EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoint + 1, false);
}
if (breakpoints.size() > 0) {
@@ -1079,8 +1079,12 @@ void ScriptEditor::_mark_built_in_scripts_as_saved(const String &p_parent_path)
}
Ref<Script> scr = edited_res;
- if (scr.is_valid() && scr->is_tool()) {
- scr->reload(true);
+ if (scr.is_valid()) {
+ trigger_live_script_reload(scr->get_path());
+
+ if (scr->is_tool()) {
+ scr->reload(true);
+ }
}
}
}
@@ -1816,6 +1820,48 @@ void ScriptEditor::notify_script_changed(const Ref<Script> &p_script) {
emit_signal(SNAME("editor_script_changed"), p_script);
}
+Vector<String> ScriptEditor::_get_breakpoints() {
+ Vector<String> ret;
+ HashSet<String> loaded_scripts;
+ for (int i = 0; i < tab_container->get_tab_count(); i++) {
+ ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
+ if (!se) {
+ continue;
+ }
+
+ Ref<Script> scr = se->get_edited_resource();
+ if (scr.is_null()) {
+ continue;
+ }
+
+ String base = scr->get_path();
+ loaded_scripts.insert(base);
+ if (base.is_empty() || base.begins_with("local://")) {
+ continue;
+ }
+
+ PackedInt32Array bpoints = se->get_breakpoints();
+ for (int32_t bpoint : bpoints) {
+ ret.push_back(base + ":" + itos((int)bpoint + 1));
+ }
+ }
+
+ // Load breakpoints that are in closed scripts.
+ List<String> cached_editors;
+ script_editor_cache->get_sections(&cached_editors);
+ for (const String &E : cached_editors) {
+ if (loaded_scripts.has(E)) {
+ continue;
+ }
+
+ Array breakpoints = _get_cached_breakpoints_for_script(E);
+ for (int breakpoint : breakpoints) {
+ ret.push_back(E + ":" + itos((int)breakpoint + 1));
+ }
+ }
+ return ret;
+}
+
void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) {
HashSet<String> loaded_scripts;
for (int i = 0; i < tab_container->get_tab_count(); i++) {
@@ -1825,19 +1871,19 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) {
}
Ref<Script> scr = se->get_edited_resource();
- if (scr == nullptr) {
+ if (scr.is_null()) {
continue;
}
String base = scr->get_path();
loaded_scripts.insert(base);
- if (base.begins_with("local://") || base.is_empty()) {
+ if (base.is_empty() || base.begins_with("local://")) {
continue;
}
PackedInt32Array bpoints = se->get_breakpoints();
- for (int j = 0; j < bpoints.size(); j++) {
- p_breakpoints->push_back(base + ":" + itos((int)bpoints[j] + 1));
+ for (int32_t bpoint : bpoints) {
+ p_breakpoints->push_back(base + ":" + itos((int)bpoint + 1));
}
}
@@ -1850,24 +1896,20 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) {
}
Array breakpoints = _get_cached_breakpoints_for_script(E);
- for (int i = 0; i < breakpoints.size(); i++) {
- p_breakpoints->push_back(E + ":" + itos((int)breakpoints[i] + 1));
+ for (int breakpoint : breakpoints) {
+ p_breakpoints->push_back(E + ":" + itos((int)breakpoint + 1));
}
}
}
void ScriptEditor::_members_overview_selected(int p_idx) {
- ScriptEditorBase *se = _get_current_editor();
- if (!se) {
- return;
+ int line = members_overview->get_item_metadata(p_idx);
+ ScriptEditorBase *current = _get_current_editor();
+ if (ScriptTextEditor *script_text_editor = Object::cast_to<ScriptTextEditor>(current)) {
+ script_text_editor->goto_line_centered(line);
+ } else if (current) {
+ current->goto_line(line);
}
- // Go to the member's line and reset the cursor column. We can't change scroll_position
- // directly until we have gone to the line first, since code might be folded.
- se->goto_line(members_overview->get_item_metadata(p_idx));
- Dictionary state = se->get_edit_state();
- state["column"] = 0;
- state["scroll_position"] = members_overview->get_item_metadata(p_idx);
- se->set_edit_state(state);
}
void ScriptEditor::_help_overview_selected(int p_idx) {
@@ -2711,9 +2753,11 @@ void ScriptEditor::apply_scripts() const {
}
void ScriptEditor::reload_scripts(bool p_refresh_only) {
- if (external_editor_active) {
- return;
- }
+ // Call deferred to make sure it runs on the main thread.
+ callable_mp(this, &ScriptEditor::_reload_scripts).call_deferred(p_refresh_only);
+}
+
+void ScriptEditor::_reload_scripts(bool p_refresh_only) {
for (int i = 0; i < tab_container->get_tab_count(); i++) {
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
if (!se) {
@@ -2942,8 +2986,8 @@ void ScriptEditor::_files_moved(const String &p_old_file, const String &p_new_fi
// If Script, update breakpoints with debugger.
Array breakpoints = _get_cached_breakpoints_for_script(p_new_file);
- for (int i = 0; i < breakpoints.size(); i++) {
- int line = (int)breakpoints[i] + 1;
+ for (int breakpoint : breakpoints) {
+ int line = (int)breakpoint + 1;
EditorDebuggerNode::get_singleton()->set_breakpoint(p_old_file, line, false);
if (!p_new_file.begins_with("local://") && ResourceLoader::exists(p_new_file, "Script")) {
EditorDebuggerNode::get_singleton()->set_breakpoint(p_new_file, line, true);
@@ -2967,8 +3011,8 @@ void ScriptEditor::_file_removed(const String &p_removed_file) {
// Check closed.
if (script_editor_cache->has_section(p_removed_file)) {
Array breakpoints = _get_cached_breakpoints_for_script(p_removed_file);
- for (int i = 0; i < breakpoints.size(); i++) {
- EditorDebuggerNode::get_singleton()->set_breakpoint(p_removed_file, (int)breakpoints[i] + 1, false);
+ for (int breakpoint : breakpoints) {
+ EditorDebuggerNode::get_singleton()->set_breakpoint(p_removed_file, (int)breakpoint + 1, false);
}
script_editor_cache->erase_section(p_removed_file);
}
@@ -3423,8 +3467,8 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) {
}
Array breakpoints = _get_cached_breakpoints_for_script(E);
- for (int i = 0; i < breakpoints.size(); i++) {
- EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, true);
+ for (int breakpoint : breakpoints) {
+ EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoint + 1, true);
}
}
@@ -3970,6 +4014,7 @@ void ScriptEditor::_bind_methods() {
ClassDB::bind_method("_help_tab_goto", &ScriptEditor::_help_tab_goto);
ClassDB::bind_method("get_current_editor", &ScriptEditor::_get_current_editor);
ClassDB::bind_method("get_open_script_editors", &ScriptEditor::_get_open_script_editors);
+ ClassDB::bind_method("get_breakpoints", &ScriptEditor::_get_breakpoints);
ClassDB::bind_method(D_METHOD("register_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::register_syntax_highlighter);
ClassDB::bind_method(D_METHOD("unregister_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::unregister_syntax_highlighter);
@@ -4061,7 +4106,7 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) {
members_overview_alphabeta_sort_button->set_tooltip_text(TTR("Toggle alphabetical sorting of the method list."));
members_overview_alphabeta_sort_button->set_toggle_mode(true);
members_overview_alphabeta_sort_button->set_pressed(EDITOR_GET("text_editor/script_list/sort_members_outline_alphabetically"));
- members_overview_alphabeta_sort_button->connect("toggled", callable_mp(this, &ScriptEditor::_toggle_members_overview_alpha_sort));
+ members_overview_alphabeta_sort_button->connect(SceneStringName(toggled), callable_mp(this, &ScriptEditor::_toggle_members_overview_alpha_sort));
buttons_hbox->add_child(members_overview_alphabeta_sort_button);
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index 9db1aff76a..e0fac5d0c6 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -55,7 +55,7 @@ class EditorSyntaxHighlighter : public SyntaxHighlighter {
GDCLASS(EditorSyntaxHighlighter, SyntaxHighlighter)
private:
- Ref<RefCounted> edited_resourse;
+ Ref<RefCounted> edited_resource;
protected:
static void _bind_methods();
@@ -67,8 +67,8 @@ public:
virtual String _get_name() const;
virtual PackedStringArray _get_supported_languages() const;
- void _set_edited_resource(const Ref<Resource> &p_res) { edited_resourse = p_res; }
- Ref<RefCounted> _get_edited_resource() { return edited_resourse; }
+ void _set_edited_resource(const Ref<Resource> &p_res) { edited_resource = p_res; }
+ Ref<RefCounted> _get_edited_resource() { return edited_resource; }
virtual Ref<EditorSyntaxHighlighter> _create() const;
};
@@ -170,7 +170,7 @@ public:
virtual Variant get_edit_state() = 0;
virtual void set_edit_state(const Variant &p_state) = 0;
virtual Variant get_navigation_state() = 0;
- virtual void goto_line(int p_line, bool p_with_error = false) = 0;
+ virtual void goto_line(int p_line, int p_column = 0) = 0;
virtual void set_executing_line(int p_line) = 0;
virtual void clear_executing_line() = 0;
virtual void trim_trailing_whitespace() = 0;
@@ -436,6 +436,7 @@ class ScriptEditor : public PanelContainer {
void _file_removed(const String &p_file);
void _autosave_scripts();
void _update_autosave_timer();
+ void _reload_scripts(bool p_refresh_only = false);
void _update_members_overview_visibility();
void _update_members_overview();
@@ -538,6 +539,7 @@ public:
_FORCE_INLINE_ bool edit(const Ref<Resource> &p_resource, bool p_grab_focus = true) { return edit(p_resource, -1, 0, p_grab_focus); }
bool edit(const Ref<Resource> &p_resource, int p_line, int p_col, bool p_grab_focus = true);
+ Vector<String> _get_breakpoints();
void get_breakpoints(List<String> *p_breakpoints);
PackedStringArray get_unsaved_scripts() const;
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 070471f3f3..34557b26b4 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -302,16 +302,14 @@ void ScriptTextEditor::_warning_clicked(const Variant &p_line) {
void ScriptTextEditor::_error_clicked(const Variant &p_line) {
if (p_line.get_type() == Variant::INT) {
- code_editor->get_text_editor()->remove_secondary_carets();
- code_editor->get_text_editor()->set_caret_line(p_line.operator int64_t());
+ goto_line_centered(p_line.operator int64_t());
} else if (p_line.get_type() == Variant::DICTIONARY) {
Dictionary meta = p_line.operator Dictionary();
const String path = meta["path"].operator String();
const int line = meta["line"].operator int64_t();
const int column = meta["column"].operator int64_t();
if (path.is_empty()) {
- code_editor->get_text_editor()->remove_secondary_carets();
- code_editor->get_text_editor()->set_caret_line(line);
+ goto_line_centered(line, column);
} else {
Ref<Resource> scr = ResourceLoader::load(path);
if (!scr.is_valid()) {
@@ -456,16 +454,16 @@ void ScriptTextEditor::tag_saved_version() {
code_editor->get_text_editor()->tag_saved_version();
}
-void ScriptTextEditor::goto_line(int p_line, bool p_with_error) {
- code_editor->goto_line(p_line);
+void ScriptTextEditor::goto_line(int p_line, int p_column) {
+ code_editor->goto_line(p_line, p_column);
}
void ScriptTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) {
code_editor->goto_line_selection(p_line, p_begin, p_end);
}
-void ScriptTextEditor::goto_line_centered(int p_line) {
- code_editor->goto_line_centered(p_line);
+void ScriptTextEditor::goto_line_centered(int p_line, int p_column) {
+ code_editor->goto_line_centered(p_line, p_column);
}
void ScriptTextEditor::set_executing_line(int p_line) {
@@ -919,8 +917,7 @@ void ScriptTextEditor::_breakpoint_item_pressed(int p_idx) {
if (p_idx < 4) { // Any item before the separator.
_edit_option(breakpoints_menu->get_item_id(p_idx));
} else {
- code_editor->goto_line(breakpoints_menu->get_item_metadata(p_idx));
- callable_mp((TextEdit *)code_editor->get_text_editor(), &TextEdit::center_viewport_to_caret).call_deferred(0); // Needs to be deferred, because goto uses call_deferred().
+ code_editor->goto_line_centered(breakpoints_menu->get_item_metadata(p_idx));
}
}
@@ -1816,9 +1813,9 @@ static String _get_dropped_resource_line(const Ref<Resource> &p_resource, bool p
}
if (is_script) {
- variable_name = variable_name.to_pascal_case().validate_identifier();
+ variable_name = variable_name.to_pascal_case().validate_ascii_identifier();
} else {
- variable_name = variable_name.to_snake_case().to_upper().validate_identifier();
+ variable_name = variable_name.to_snake_case().to_upper().validate_ascii_identifier();
}
return vformat("const %s = preload(%s)", variable_name, _quote_drop_data(path));
}
@@ -1932,13 +1929,13 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
path = sn->get_path_to(node);
}
for (const String &segment : path.split("/")) {
- if (!segment.is_valid_identifier()) {
+ if (!segment.is_valid_ascii_identifier()) {
path = _quote_drop_data(path);
break;
}
}
- String variable_name = String(node->get_name()).to_snake_case().validate_identifier();
+ String variable_name = String(node->get_name()).to_snake_case().validate_ascii_identifier();
if (use_type) {
StringName class_name = node->get_class_name();
Ref<Script> node_script = node->get_script();
@@ -1975,7 +1972,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
}
for (const String &segment : path.split("/")) {
- if (!segment.is_valid_identifier()) {
+ if (!segment.is_valid_ascii_identifier()) {
path = _quote_drop_data(path);
break;
}
@@ -2376,6 +2373,7 @@ void ScriptTextEditor::_enable_code_editor() {
ScriptTextEditor::ScriptTextEditor() {
code_editor = memnew(CodeTextEditor);
+ code_editor->set_toggle_list_control(ScriptEditor::get_singleton()->get_left_list_split());
code_editor->add_theme_constant_override("separation", 2);
code_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
code_editor->set_code_complete_func(_code_complete_scripts, this);
diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h
index 8c2ec1561b..7aa0726479 100644
--- a/editor/plugins/script_text_editor.h
+++ b/editor/plugins/script_text_editor.h
@@ -234,9 +234,9 @@ public:
virtual void convert_indent() override;
virtual void tag_saved_version() override;
- virtual void goto_line(int p_line, bool p_with_error = false) override;
+ virtual void goto_line(int p_line, int p_column = 0) override;
void goto_line_selection(int p_line, int p_begin, int p_end);
- void goto_line_centered(int p_line);
+ void goto_line_centered(int p_line, int p_column = 0);
virtual void set_executing_line(int p_line) override;
virtual void clear_executing_line() override;
diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp
index 02cb76ac10..ea049756b7 100644
--- a/editor/plugins/shader_editor_plugin.cpp
+++ b/editor/plugins/shader_editor_plugin.cpp
@@ -45,6 +45,16 @@
#include "scene/gui/item_list.h"
#include "scene/gui/texture_rect.h"
+Ref<Resource> ShaderEditorPlugin::_get_current_shader() {
+ int index = shader_tabs->get_current_tab();
+ ERR_FAIL_INDEX_V(index, shader_tabs->get_tab_count(), Ref<Resource>());
+ if (edited_shaders[index].shader.is_valid()) {
+ return edited_shaders[index].shader;
+ } else {
+ return edited_shaders[index].shader_inc;
+ }
+}
+
void ShaderEditorPlugin::_update_shader_list() {
shader_list->clear();
for (EditedShader &edited_shader : edited_shaders) {
@@ -93,9 +103,7 @@ void ShaderEditorPlugin::_update_shader_list() {
shader_list->select(shader_tabs->get_current_tab());
}
- for (int i = FILE_SAVE; i < FILE_MAX; i++) {
- file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), edited_shaders.is_empty());
- }
+ _set_file_specific_items_disabled(edited_shaders.is_empty());
_update_shader_list_status();
}
@@ -141,7 +149,9 @@ void ShaderEditorPlugin::edit(Object *p_object) {
}
}
es.shader_inc = Ref<ShaderInclude>(si);
- es.shader_editor = memnew(TextShaderEditor);
+ TextShaderEditor *text_shader = memnew(TextShaderEditor);
+ text_shader->get_code_editor()->set_toggle_list_control(left_panel);
+ es.shader_editor = text_shader;
es.shader_editor->edit_shader_include(si);
shader_tabs->add_child(es.shader_editor);
} else {
@@ -158,7 +168,9 @@ void ShaderEditorPlugin::edit(Object *p_object) {
if (vs.is_valid()) {
es.shader_editor = memnew(VisualShaderEditor);
} else {
- es.shader_editor = memnew(TextShaderEditor);
+ TextShaderEditor *text_shader = memnew(TextShaderEditor);
+ text_shader->get_code_editor()->set_toggle_list_control(left_panel);
+ es.shader_editor = text_shader;
}
shader_tabs->add_child(es.shader_editor);
es.shader_editor->edit_shader(es.shader);
@@ -362,6 +374,61 @@ void ShaderEditorPlugin::_shader_list_clicked(int p_item, Vector2 p_local_mouse_
if (p_mouse_button_index == MouseButton::MIDDLE) {
_close_shader(p_item);
}
+ if (p_mouse_button_index == MouseButton::RIGHT) {
+ _make_script_list_context_menu();
+ }
+}
+
+void ShaderEditorPlugin::_setup_popup_menu(PopupMenuType p_type, PopupMenu *p_menu) {
+ if (p_type == FILE) {
+ p_menu->add_shortcut(ED_SHORTCUT("shader_editor/new", TTR("New Shader..."), KeyModifierMask::CMD_OR_CTRL | Key::N), FILE_NEW);
+ p_menu->add_shortcut(ED_SHORTCUT("shader_editor/new_include", TTR("New Shader Include..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::N), FILE_NEW_INCLUDE);
+ p_menu->add_separator();
+ p_menu->add_shortcut(ED_SHORTCUT("shader_editor/open", TTR("Load Shader File..."), KeyModifierMask::CMD_OR_CTRL | Key::O), FILE_OPEN);
+ p_menu->add_shortcut(ED_SHORTCUT("shader_editor/open_include", TTR("Load Shader Include File..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::O), FILE_OPEN_INCLUDE);
+ }
+
+ if (p_type == FILE || p_type == CONTEXT_VALID_ITEM) {
+ p_menu->add_shortcut(ED_SHORTCUT("shader_editor/save", TTR("Save File"), KeyModifierMask::ALT | KeyModifierMask::CMD_OR_CTRL | Key::S), FILE_SAVE);
+ p_menu->add_shortcut(ED_SHORTCUT("shader_editor/save_as", TTR("Save File As...")), FILE_SAVE_AS);
+ }
+
+ if (p_type == FILE) {
+ p_menu->add_separator();
+ p_menu->add_item(TTR("Open File in Inspector"), FILE_INSPECT);
+ p_menu->add_separator();
+ p_menu->add_shortcut(ED_SHORTCUT("shader_editor/close_file", TTR("Close File"), KeyModifierMask::CMD_OR_CTRL | Key::W), FILE_CLOSE);
+ } else {
+ p_menu->add_shortcut(ED_SHORTCUT("shader_editor/close_file", TTR("Close File"), KeyModifierMask::CMD_OR_CTRL | Key::W), FILE_CLOSE);
+ p_menu->add_item(TTR("Close All"), CLOSE_ALL);
+ p_menu->add_item(TTR("Close Other Tabs"), CLOSE_OTHER_TABS);
+ if (p_type == CONTEXT_VALID_ITEM) {
+ p_menu->add_separator();
+ p_menu->add_item(TTR("Copy Script Path"), COPY_PATH);
+ p_menu->add_item(TTR("Show in File System"), SHOW_IN_FILE_SYSTEM);
+ }
+ }
+}
+
+void ShaderEditorPlugin::_make_script_list_context_menu() {
+ context_menu->clear();
+
+ int selected = shader_tabs->get_current_tab();
+ if (selected < 0 || selected >= shader_tabs->get_tab_count()) {
+ return;
+ }
+
+ Control *control = shader_tabs->get_tab_control(selected);
+ bool is_valid_editor_control = Object::cast_to<TextShaderEditor>(control) || Object::cast_to<VisualShaderEditor>(control);
+
+ _setup_popup_menu(is_valid_editor_control ? CONTEXT_VALID_ITEM : CONTEXT, context_menu);
+
+ context_menu->set_item_disabled(context_menu->get_item_index(CLOSE_ALL), shader_tabs->get_tab_count() <= 0);
+ context_menu->set_item_disabled(context_menu->get_item_index(CLOSE_OTHER_TABS), shader_tabs->get_tab_count() <= 1);
+
+ context_menu->set_position(main_split->get_screen_position() + main_split->get_local_mouse_position());
+ context_menu->reset_size();
+ context_menu->popup();
}
void ShaderEditorPlugin::_close_shader(int p_index) {
@@ -371,6 +438,10 @@ void ShaderEditorPlugin::_close_shader(int p_index) {
edited_shaders.remove_at(p_index);
_update_shader_list();
EditorUndoRedoManager::get_singleton()->clear_history(); // To prevent undo on deleted graphs.
+
+ if (shader_tabs->get_tab_count() == 0) {
+ left_panel->show(); // Make sure the panel is visible, because it can't be toggled without open shaders.
+ }
}
void ShaderEditorPlugin::_close_builtin_shaders_from_scene(const String &p_scene) {
@@ -486,6 +557,31 @@ void ShaderEditorPlugin::_menu_item_pressed(int p_index) {
case FILE_CLOSE: {
_close_shader(shader_tabs->get_current_tab());
} break;
+ case CLOSE_ALL: {
+ while (shader_tabs->get_tab_count() > 0) {
+ _close_shader(0);
+ }
+ } break;
+ case CLOSE_OTHER_TABS: {
+ int index = shader_tabs->get_current_tab();
+ for (int i = 0; i < index; i++) {
+ _close_shader(0);
+ }
+ while (shader_tabs->get_tab_count() > 1) {
+ _close_shader(1);
+ }
+ } break;
+ case SHOW_IN_FILE_SYSTEM: {
+ Ref<Resource> shader = _get_current_shader();
+ String path = shader->get_path();
+ if (!path.is_empty()) {
+ FileSystemDock::get_singleton()->navigate_to_path(path);
+ }
+ } break;
+ case COPY_PATH: {
+ Ref<Resource> shader = _get_current_shader();
+ DisplayServer::get_singleton()->clipboard_set(shader->get_path());
+ } break;
}
}
@@ -652,6 +748,14 @@ void ShaderEditorPlugin::_res_saved_callback(const Ref<Resource> &p_res) {
}
}
+void ShaderEditorPlugin::_set_file_specific_items_disabled(bool p_disabled) {
+ PopupMenu *file_popup_menu = file_menu->get_popup();
+ file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_SAVE), p_disabled);
+ file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_SAVE_AS), p_disabled);
+ file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_INSPECT), p_disabled);
+ file_popup_menu->set_item_disabled(file_popup_menu->get_item_index(FILE_CLOSE), p_disabled);
+}
+
void ShaderEditorPlugin::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY: {
@@ -672,30 +776,22 @@ ShaderEditorPlugin::ShaderEditorPlugin() {
Ref<Shortcut> make_floating_shortcut = ED_SHORTCUT_AND_COMMAND("shader_editor/make_floating", TTR("Make Floating"));
window_wrapper->set_wrapped_control(main_split, make_floating_shortcut);
- VBoxContainer *vb = memnew(VBoxContainer);
+ left_panel = memnew(VBoxContainer);
HBoxContainer *menu_hb = memnew(HBoxContainer);
- vb->add_child(menu_hb);
+ left_panel->add_child(menu_hb);
file_menu = memnew(MenuButton);
file_menu->set_text(TTR("File"));
file_menu->set_shortcut_context(main_split);
- file_menu->get_popup()->add_shortcut(ED_SHORTCUT("shader_editor/new", TTR("New Shader..."), KeyModifierMask::CMD_OR_CTRL | Key::N), FILE_NEW);
- file_menu->get_popup()->add_shortcut(ED_SHORTCUT("shader_editor/new_include", TTR("New Shader Include..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::N), FILE_NEW_INCLUDE);
- file_menu->get_popup()->add_separator();
- file_menu->get_popup()->add_shortcut(ED_SHORTCUT("shader_editor/open", TTR("Load Shader File..."), KeyModifierMask::CMD_OR_CTRL | Key::O), FILE_OPEN);
- file_menu->get_popup()->add_shortcut(ED_SHORTCUT("shader_editor/open_include", TTR("Load Shader Include File..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::O), FILE_OPEN_INCLUDE);
- file_menu->get_popup()->add_shortcut(ED_SHORTCUT("shader_editor/save", TTR("Save File"), KeyModifierMask::ALT | KeyModifierMask::CMD_OR_CTRL | Key::S), FILE_SAVE);
- file_menu->get_popup()->add_shortcut(ED_SHORTCUT("shader_editor/save_as", TTR("Save File As...")), FILE_SAVE_AS);
- file_menu->get_popup()->add_separator();
- file_menu->get_popup()->add_item(TTR("Open File in Inspector"), FILE_INSPECT);
- file_menu->get_popup()->add_separator();
- file_menu->get_popup()->add_shortcut(ED_SHORTCUT("shader_editor/close_file", TTR("Close File"), KeyModifierMask::CMD_OR_CTRL | Key::W), FILE_CLOSE);
+ _setup_popup_menu(FILE, file_menu->get_popup());
file_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ShaderEditorPlugin::_menu_item_pressed));
menu_hb->add_child(file_menu);
- for (int i = FILE_SAVE; i < FILE_MAX; i++) {
- file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), true);
- }
+ _set_file_specific_items_disabled(true);
+
+ context_menu = memnew(PopupMenu);
+ add_child(context_menu);
+ context_menu->connect(SceneStringName(id_pressed), callable_mp(this, &ShaderEditorPlugin::_menu_item_pressed));
Control *padding = memnew(Control);
padding->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -715,13 +811,14 @@ ShaderEditorPlugin::ShaderEditorPlugin() {
shader_list = memnew(ItemList);
shader_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
shader_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
- vb->add_child(shader_list);
+ left_panel->add_child(shader_list);
shader_list->connect(SceneStringName(item_selected), callable_mp(this, &ShaderEditorPlugin::_shader_selected));
shader_list->connect("item_clicked", callable_mp(this, &ShaderEditorPlugin::_shader_list_clicked));
+ shader_list->set_allow_rmb_select(true);
SET_DRAG_FORWARDING_GCD(shader_list, ShaderEditorPlugin);
- main_split->add_child(vb);
- vb->set_custom_minimum_size(Size2(200, 300) * EDSCALE);
+ main_split->add_child(left_panel);
+ left_panel->set_custom_minimum_size(Size2(200, 300) * EDSCALE);
shader_tabs = memnew(TabContainer);
shader_tabs->set_tabs_visible(false);
@@ -734,7 +831,7 @@ ShaderEditorPlugin::ShaderEditorPlugin() {
button = EditorNode::get_bottom_panel()->add_item(TTR("Shader Editor"), window_wrapper, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_shader_editor_bottom_panel", TTR("Toggle Shader Editor Bottom Panel"), KeyModifierMask::ALT | Key::S));
shader_create_dialog = memnew(ShaderCreateDialog);
- vb->add_child(shader_create_dialog);
+ main_split->add_child(shader_create_dialog);
shader_create_dialog->connect("shader_created", callable_mp(this, &ShaderEditorPlugin::_shader_created));
shader_create_dialog->connect("shader_include_created", callable_mp(this, &ShaderEditorPlugin::_shader_include_created));
}
diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h
index f9b9405e45..43e6af79fa 100644
--- a/editor/plugins/shader_editor_plugin.h
+++ b/editor/plugins/shader_editor_plugin.h
@@ -40,6 +40,7 @@ class ShaderCreateDialog;
class ShaderEditor;
class TabContainer;
class TextShaderEditor;
+class VBoxContainer;
class VisualShaderEditor;
class WindowWrapper;
@@ -60,8 +61,6 @@ class ShaderEditorPlugin : public EditorPlugin {
LocalVector<EditedShader> edited_shaders;
- // Always valid operations come first in the enum, file-specific ones
- // should go after FILE_SAVE which is used to build the menu accordingly.
enum {
FILE_NEW,
FILE_NEW_INCLUDE,
@@ -71,15 +70,26 @@ class ShaderEditorPlugin : public EditorPlugin {
FILE_SAVE_AS,
FILE_INSPECT,
FILE_CLOSE,
- FILE_MAX
+ CLOSE_ALL,
+ CLOSE_OTHER_TABS,
+ SHOW_IN_FILE_SYSTEM,
+ COPY_PATH,
+ };
+
+ enum PopupMenuType {
+ FILE,
+ CONTEXT,
+ CONTEXT_VALID_ITEM,
};
HSplitContainer *main_split = nullptr;
+ VBoxContainer *left_panel = nullptr;
ItemList *shader_list = nullptr;
TabContainer *shader_tabs = nullptr;
Button *button = nullptr;
MenuButton *file_menu = nullptr;
+ PopupMenu *context_menu = nullptr;
WindowWrapper *window_wrapper = nullptr;
Button *make_floating = nullptr;
@@ -88,15 +98,19 @@ class ShaderEditorPlugin : public EditorPlugin {
float text_shader_zoom_factor = 1.0f;
+ Ref<Resource> _get_current_shader();
void _update_shader_list();
void _shader_selected(int p_index);
void _shader_list_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index);
+ void _setup_popup_menu(PopupMenuType p_type, PopupMenu *p_menu);
+ void _make_script_list_context_menu();
void _menu_item_pressed(int p_index);
void _resource_saved(Object *obj);
void _close_shader(int p_index);
void _close_builtin_shaders_from_scene(const String &p_scene);
void _file_removed(const String &p_removed_file);
void _res_saved_callback(const Ref<Resource> &p_res);
+ void _set_file_specific_items_disabled(bool p_disabled);
void _shader_created(Ref<Shader> p_shader);
void _shader_include_created(Ref<ShaderInclude> p_shader_inc);
diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp
index b340dd976e..dc4d4db3f8 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_3d_editor_plugin.cpp
@@ -47,6 +47,7 @@
#include "scene/3d/physics/physics_body_3d.h"
#include "scene/gui/separator.h"
#include "scene/gui/texture_rect.h"
+#include "scene/property_utils.h"
#include "scene/resources/3d/capsule_shape_3d.h"
#include "scene/resources/skeleton_profile.h"
#include "scene/resources/surface_tool.h"
@@ -65,7 +66,7 @@ void BoneTransformEditor::create_editors() {
// Position property.
position_property = memnew(EditorPropertyVector3());
- position_property->setup(-10000, 10000, 0.001f, true);
+ position_property->setup(-10000, 10000, 0.001, true);
position_property->set_label("Position");
position_property->set_selectable(false);
position_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
@@ -74,7 +75,7 @@ void BoneTransformEditor::create_editors() {
// Rotation property.
rotation_property = memnew(EditorPropertyQuaternion());
- rotation_property->setup(-10000, 10000, 0.001f, true);
+ rotation_property->setup(-10000, 10000, 0.001, true);
rotation_property->set_label("Rotation");
rotation_property->set_selectable(false);
rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
@@ -83,7 +84,7 @@ void BoneTransformEditor::create_editors() {
// Scale property.
scale_property = memnew(EditorPropertyVector3());
- scale_property->setup(-10000, 10000, 0.001f, true);
+ scale_property->setup(-10000, 10000, 0.001, true, true);
scale_property->set_label("Scale");
scale_property->set_selectable(false);
scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
@@ -97,7 +98,7 @@ void BoneTransformEditor::create_editors() {
// Transform/Matrix property.
rest_matrix = memnew(EditorPropertyTransform3D());
- rest_matrix->setup(-10000, 10000, 0.001f, true);
+ rest_matrix->setup(-10000, 10000, 0.001, true);
rest_matrix->set_label("Transform");
rest_matrix->set_selectable(false);
rest_section->get_vbox()->add_child(rest_matrix);
@@ -122,6 +123,13 @@ void BoneTransformEditor::_value_changed(const String &p_property, const Variant
undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS);
undo_redo->add_undo_property(skeleton, p_property, skeleton->get(p_property));
undo_redo->add_do_property(skeleton, p_property, p_value);
+
+ Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
+ if (se) {
+ undo_redo->add_do_method(se, "update_joint_tree");
+ undo_redo->add_undo_method(se, "update_joint_tree");
+ }
+
undo_redo->commit_action();
}
}
@@ -189,26 +197,31 @@ void BoneTransformEditor::_update_properties() {
if (split[2] == "enabled") {
enabled_checkbox->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
enabled_checkbox->update_property();
+ enabled_checkbox->update_editor_property_status();
enabled_checkbox->queue_redraw();
}
if (split[2] == "position") {
position_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
position_property->update_property();
+ position_property->update_editor_property_status();
position_property->queue_redraw();
}
if (split[2] == "rotation") {
rotation_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
rotation_property->update_property();
+ rotation_property->update_editor_property_status();
rotation_property->queue_redraw();
}
if (split[2] == "scale") {
scale_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
scale_property->update_property();
+ scale_property->update_editor_property_status();
scale_property->queue_redraw();
}
if (split[2] == "rest") {
rest_matrix->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
rest_matrix->update_property();
+ rest_matrix->update_editor_property_status();
rest_matrix->queue_redraw();
}
}
@@ -232,6 +245,11 @@ void Skeleton3DEditor::set_bone_options_enabled(const bool p_bone_options_enable
skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_SELECTED_POSES_TO_RESTS, !p_bone_options_enabled);
};
+void Skeleton3DEditor::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("update_all"), &Skeleton3DEditor::update_all);
+ ClassDB::bind_method(D_METHOD("update_joint_tree"), &Skeleton3DEditor::update_joint_tree);
+}
+
void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) {
if (!skeleton) {
return;
@@ -294,6 +312,10 @@ void Skeleton3DEditor::reset_pose(const bool p_all_bones) {
ur->add_undo_method(skeleton, "set_bone_pose_scale", selected_bone, skeleton->get_bone_pose_scale(selected_bone));
ur->add_do_method(skeleton, "reset_bone_pose", selected_bone);
}
+
+ ur->add_undo_method(this, "update_joint_tree");
+ ur->add_do_method(this, "update_joint_tree");
+
ur->commit_action();
}
@@ -357,6 +379,10 @@ void Skeleton3DEditor::pose_to_rest(const bool p_all_bones) {
ur->add_do_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_pose(selected_bone));
ur->add_undo_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone));
}
+
+ ur->add_undo_method(this, "update_joint_tree");
+ ur->add_do_method(this, "update_joint_tree");
+
ur->commit_action();
}
@@ -620,9 +646,12 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se
}
ur->add_undo_method(skeleton_node, "set_bone_parent", p_selected_boneidx, skeleton_node->get_bone_parent(p_selected_boneidx));
ur->add_do_method(skeleton_node, "set_bone_parent", p_selected_boneidx, p_target_boneidx);
+
+ ur->add_undo_method(this, "update_joint_tree");
+ ur->add_do_method(this, "update_joint_tree");
+
skeleton_node->set_bone_parent(p_selected_boneidx, p_target_boneidx);
- update_joint_tree();
ur->commit_action();
}
@@ -655,6 +684,107 @@ void Skeleton3DEditor::_joint_tree_selection_changed() {
void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button) {
}
+void Skeleton3DEditor::_joint_tree_button_clicked(Object *p_item, int p_column, int p_id, MouseButton p_button) {
+ if (!skeleton) {
+ return;
+ }
+
+ TreeItem *tree_item = Object::cast_to<TreeItem>(p_item);
+ if (tree_item) {
+ String tree_item_metadata = tree_item->get_metadata(0);
+
+ String bone_enabled_property = tree_item_metadata + "/enabled";
+ String bone_parent_property = tree_item_metadata + "/parent";
+ String bone_name_property = tree_item_metadata + "/name";
+ String bone_position_property = tree_item_metadata + "/position";
+ String bone_rotation_property = tree_item_metadata + "/rotation";
+ String bone_scale_property = tree_item_metadata + "/scale";
+ String bone_rest_property = tree_item_metadata + "/rest";
+
+ Variant current_enabled = skeleton->get(bone_enabled_property);
+ Variant current_parent = skeleton->get(bone_parent_property);
+ Variant current_name = skeleton->get(bone_name_property);
+ Variant current_position = skeleton->get(bone_position_property);
+ Variant current_rotation = skeleton->get(bone_rotation_property);
+ Variant current_scale = skeleton->get(bone_scale_property);
+ Variant current_rest = skeleton->get(bone_rest_property);
+
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(TTR("Revert Bone"));
+
+ bool can_revert_enabled = EditorPropertyRevert::can_property_revert(skeleton, bone_enabled_property, &current_enabled);
+ if (can_revert_enabled) {
+ bool is_valid = false;
+ Variant new_enabled = EditorPropertyRevert::get_property_revert_value(skeleton, bone_enabled_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_enabled_property, current_enabled);
+ ur->add_do_method(skeleton, "set", bone_enabled_property, new_enabled);
+ }
+ }
+
+ bool can_revert_parent = EditorPropertyRevert::can_property_revert(skeleton, bone_parent_property, &current_parent);
+ if (can_revert_parent) {
+ bool is_valid = false;
+ Variant new_parent = EditorPropertyRevert::get_property_revert_value(skeleton, bone_parent_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_parent_property, current_parent);
+ ur->add_do_method(skeleton, "set", bone_parent_property, new_parent);
+ }
+ }
+ bool can_revert_name = EditorPropertyRevert::can_property_revert(skeleton, bone_name_property, &current_name);
+ if (can_revert_name) {
+ bool is_valid = false;
+ Variant new_name = EditorPropertyRevert::get_property_revert_value(skeleton, bone_name_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_name_property, current_name);
+ ur->add_do_method(skeleton, "set", bone_name_property, new_name);
+ }
+ }
+ bool can_revert_position = EditorPropertyRevert::can_property_revert(skeleton, bone_position_property, &current_position);
+ if (can_revert_position) {
+ bool is_valid = false;
+ Variant new_position = EditorPropertyRevert::get_property_revert_value(skeleton, bone_position_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_position_property, current_position);
+ ur->add_do_method(skeleton, "set", bone_position_property, new_position);
+ }
+ }
+ bool can_revert_rotation = EditorPropertyRevert::can_property_revert(skeleton, bone_rotation_property, &current_rotation);
+ if (can_revert_rotation) {
+ bool is_valid = false;
+ Variant new_rotation = EditorPropertyRevert::get_property_revert_value(skeleton, bone_rotation_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_rotation_property, current_rotation);
+ ur->add_do_method(skeleton, "set", bone_rotation_property, new_rotation);
+ }
+ }
+ bool can_revert_scale = EditorPropertyRevert::can_property_revert(skeleton, bone_scale_property, &current_scale);
+ if (can_revert_scale) {
+ bool is_valid = false;
+ Variant new_scale = EditorPropertyRevert::get_property_revert_value(skeleton, bone_scale_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_scale_property, current_scale);
+ ur->add_do_method(skeleton, "set", bone_scale_property, new_scale);
+ }
+ }
+ bool can_revert_rest = EditorPropertyRevert::can_property_revert(skeleton, bone_rest_property, &current_rest);
+ if (can_revert_rest) {
+ bool is_valid = false;
+ Variant new_rest = EditorPropertyRevert::get_property_revert_value(skeleton, bone_rest_property, &is_valid);
+ if (is_valid) {
+ ur->add_undo_method(skeleton, "set", bone_rest_property, current_rest);
+ ur->add_do_method(skeleton, "set", bone_rest_property, new_rest);
+ }
+ }
+
+ ur->add_undo_method(this, "update_all");
+ ur->add_do_method(this, "update_all");
+
+ ur->commit_action();
+ }
+ return;
+}
+
void Skeleton3DEditor::_update_properties() {
if (pose_editor) {
pose_editor->_update_properties();
@@ -693,15 +823,52 @@ void Skeleton3DEditor::update_joint_tree() {
joint_item->set_selectable(0, true);
joint_item->set_metadata(0, "bones/" + itos(current_bone_idx));
+ String bone_enabled_property = "bones/" + itos(current_bone_idx) + "/enabled";
+ String bone_parent_property = "bones/" + itos(current_bone_idx) + "/parent";
+ String bone_name_property = "bones/" + itos(current_bone_idx) + "/name";
+ String bone_position_property = "bones/" + itos(current_bone_idx) + "/position";
+ String bone_rotation_property = "bones/" + itos(current_bone_idx) + "/rotation";
+ String bone_scale_property = "bones/" + itos(current_bone_idx) + "/scale";
+ String bone_rest_property = "bones/" + itos(current_bone_idx) + "/rest";
+
+ Variant current_enabled = skeleton->get(bone_enabled_property);
+ Variant current_parent = skeleton->get(bone_parent_property);
+ Variant current_name = skeleton->get(bone_name_property);
+ Variant current_position = skeleton->get(bone_position_property);
+ Variant current_rotation = skeleton->get(bone_rotation_property);
+ Variant current_scale = skeleton->get(bone_scale_property);
+ Variant current_rest = skeleton->get(bone_rest_property);
+
+ bool can_revert_enabled = EditorPropertyRevert::can_property_revert(skeleton, bone_enabled_property, &current_enabled);
+ bool can_revert_parent = EditorPropertyRevert::can_property_revert(skeleton, bone_parent_property, &current_parent);
+ bool can_revert_name = EditorPropertyRevert::can_property_revert(skeleton, bone_name_property, &current_name);
+ bool can_revert_position = EditorPropertyRevert::can_property_revert(skeleton, bone_position_property, &current_position);
+ bool can_revert_rotation = EditorPropertyRevert::can_property_revert(skeleton, bone_rotation_property, &current_rotation);
+ bool can_revert_scale = EditorPropertyRevert::can_property_revert(skeleton, bone_scale_property, &current_scale);
+ bool can_revert_rest = EditorPropertyRevert::can_property_revert(skeleton, bone_rest_property, &current_rest);
+
+ if (can_revert_enabled || can_revert_parent || can_revert_name || can_revert_position || can_revert_rotation || can_revert_scale || can_revert_rest) {
+ joint_item->add_button(0, get_editor_theme_icon(SNAME("ReloadSmall")), JOINT_BUTTON_REVERT, false, TTR("Revert"));
+ }
+
// Add the bone's children to the list of bones to be processed.
Vector<int> current_bone_child_bones = skeleton->get_bone_children(current_bone_idx);
int child_bone_size = current_bone_child_bones.size();
for (int i = 0; i < child_bone_size; i++) {
bones_to_process.push_back(current_bone_child_bones[i]);
}
+
+ if (current_bone_idx == selected_bone) {
+ joint_item->select(0);
+ }
}
}
+void Skeleton3DEditor::update_all() {
+ _update_properties();
+ update_joint_tree();
+}
+
void Skeleton3DEditor::create_editors() {
set_h_size_flags(SIZE_EXPAND_FILL);
set_focus_mode(FOCUS_ALL);
@@ -747,7 +914,7 @@ void Skeleton3DEditor::create_editors() {
edit_mode_button->set_toggle_mode(true);
edit_mode_button->set_focus_mode(FOCUS_NONE);
edit_mode_button->set_tooltip_text(TTR("Edit Mode\nShow buttons on joints."));
- edit_mode_button->connect("toggled", callable_mp(this, &Skeleton3DEditor::edit_mode_toggled));
+ edit_mode_button->connect(SceneStringName(toggled), callable_mp(this, &Skeleton3DEditor::edit_mode_toggled));
edit_mode = false;
@@ -840,6 +1007,7 @@ void Skeleton3DEditor::_notification(int p_what) {
joint_tree->connect(SceneStringName(item_selected), callable_mp(this, &Skeleton3DEditor::_joint_tree_selection_changed));
joint_tree->connect("item_mouse_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select));
+ joint_tree->connect("button_clicked", callable_mp(this, &Skeleton3DEditor::_joint_tree_button_clicked));
#ifdef TOOLS_ENABLED
skeleton->connect(SceneStringName(pose_updated), callable_mp(this, &Skeleton3DEditor::_draw_gizmo));
skeleton->connect(SceneStringName(pose_updated), callable_mp(this, &Skeleton3DEditor::_update_properties));
@@ -1337,6 +1505,10 @@ void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, c
ur->add_undo_method(skeleton, "set_bone_pose_scale", p_ids[i], se->get_bone_original_scale());
}
}
+
+ ur->add_do_method(se, "update_joint_tree");
+ ur->add_undo_method(se, "update_joint_tree");
+
ur->commit_action();
}
diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h
index 79dc16ae2f..0bb58aac23 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.h
+++ b/editor/plugins/skeleton_3d_editor_plugin.h
@@ -96,6 +96,8 @@ public:
class Skeleton3DEditor : public VBoxContainer {
GDCLASS(Skeleton3DEditor, VBoxContainer);
+ static void _bind_methods();
+
friend class Skeleton3DEditorPlugin;
enum SkeletonOption {
@@ -116,6 +118,10 @@ class Skeleton3DEditor : public VBoxContainer {
Skeleton3D *skeleton = nullptr;
+ enum {
+ JOINT_BUTTON_REVERT = 0,
+ };
+
Tree *joint_tree = nullptr;
BoneTransformEditor *rest_editor = nullptr;
BoneTransformEditor *pose_editor = nullptr;
@@ -149,6 +155,7 @@ class Skeleton3DEditor : public VBoxContainer {
EditorFileDialog *file_export_lib = nullptr;
void update_joint_tree();
+ void update_all();
void create_editors();
@@ -189,6 +196,7 @@ class Skeleton3DEditor : public VBoxContainer {
void _joint_tree_selection_changed();
void _joint_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button);
+ void _joint_tree_button_clicked(Object *p_item, int p_column, int p_id, MouseButton p_button);
void _update_properties();
void _subgizmo_selection_change();
diff --git a/editor/plugins/style_box_editor_plugin.cpp b/editor/plugins/style_box_editor_plugin.cpp
index 6ecbff3bb4..0b53c10fab 100644
--- a/editor/plugins/style_box_editor_plugin.cpp
+++ b/editor/plugins/style_box_editor_plugin.cpp
@@ -113,7 +113,7 @@ StyleBoxPreview::StyleBoxPreview() {
// This theme variation works better than the normal theme because there's no focus highlight.
grid_preview->set_theme_type_variation("PreviewLightButton");
grid_preview->set_toggle_mode(true);
- grid_preview->connect("toggled", callable_mp(this, &StyleBoxPreview::_grid_preview_toggled));
+ grid_preview->connect(SceneStringName(toggled), callable_mp(this, &StyleBoxPreview::_grid_preview_toggled));
grid_preview->set_pressed(grid_preview_enabled);
add_child(grid_preview);
}
diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp
index ecdc4acf47..c1bcd43b2e 100644
--- a/editor/plugins/text_editor.cpp
+++ b/editor/plugins/text_editor.cpp
@@ -35,6 +35,7 @@
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "scene/gui/menu_button.h"
+#include "scene/gui/split_container.h"
void TextEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) {
ERR_FAIL_COND(p_highlighter.is_null());
@@ -304,8 +305,8 @@ void TextEditor::tag_saved_version() {
code_editor->get_text_editor()->tag_saved_version();
}
-void TextEditor::goto_line(int p_line, bool p_with_error) {
- code_editor->goto_line(p_line);
+void TextEditor::goto_line(int p_line, int p_column) {
+ code_editor->goto_line(p_line, p_column);
}
void TextEditor::goto_line_selection(int p_line, int p_begin, int p_end) {
@@ -606,6 +607,7 @@ TextEditor::TextEditor() {
code_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
code_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
code_editor->show_toggle_scripts_button();
+ code_editor->set_toggle_list_control(ScriptEditor::get_singleton()->get_left_list_split());
update_settings();
diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h
index 268e5c32b4..1acec4e959 100644
--- a/editor/plugins/text_editor.h
+++ b/editor/plugins/text_editor.h
@@ -129,7 +129,7 @@ public:
virtual PackedInt32Array get_breakpoints() override;
virtual void set_breakpoint(int p_line, bool p_enabled) override{};
virtual void clear_breakpoints() override{};
- virtual void goto_line(int p_line, bool p_with_error = false) override;
+ virtual void goto_line(int p_line, int p_column = 0) override;
void goto_line_selection(int p_line, int p_begin, int p_end);
virtual void set_executing_line(int p_line) override;
virtual void clear_executing_line() override;
diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp
index 6cd92bdf57..0ff7aaa3fe 100644
--- a/editor/plugins/text_shader_editor.cpp
+++ b/editor/plugins/text_shader_editor.cpp
@@ -779,7 +779,7 @@ void TextShaderEditor::_show_warnings_panel(bool p_show) {
void TextShaderEditor::_warning_clicked(const Variant &p_line) {
if (p_line.get_type() == Variant::INT) {
- code_editor->get_text_editor()->set_caret_line(p_line.operator int64_t());
+ code_editor->goto_line_centered(p_line.operator int64_t());
}
}
@@ -1256,4 +1256,5 @@ TextShaderEditor::TextShaderEditor() {
add_child(disk_changed);
_editor_settings_changed();
+ code_editor->show_toggle_scripts_button(); // TODO: Disabled for now, because it doesn't work properly.
}
diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp
index 99635a2531..ea1756b65a 100644
--- a/editor/plugins/theme_editor_plugin.cpp
+++ b/editor/plugins/theme_editor_plugin.cpp
@@ -2174,8 +2174,26 @@ void ThemeTypeDialog::_add_type_filter_cbk(const String &p_value) {
_update_add_type_options(p_value);
}
+void ThemeTypeDialog::_type_filter_input(const Ref<InputEvent> &p_ie) {
+ Ref<InputEventKey> k = p_ie;
+ if (k.is_valid() && k->is_pressed()) {
+ switch (k->get_keycode()) {
+ case Key::UP:
+ case Key::DOWN:
+ case Key::PAGEUP:
+ case Key::PAGEDOWN: {
+ add_type_options->gui_input(k);
+ add_type_filter->accept_event();
+ } break;
+ default:
+ break;
+ }
+ }
+}
+
void ThemeTypeDialog::_add_type_options_cbk(int p_index) {
add_type_filter->set_text(add_type_options->get_item_text(p_index));
+ add_type_filter->set_caret_column(add_type_filter->get_text().length());
}
void ThemeTypeDialog::_add_type_dialog_entered(const String &p_value) {
@@ -2245,6 +2263,7 @@ ThemeTypeDialog::ThemeTypeDialog() {
add_type_vb->add_child(add_type_filter);
add_type_filter->connect(SceneStringName(text_changed), callable_mp(this, &ThemeTypeDialog::_add_type_filter_cbk));
add_type_filter->connect("text_submitted", callable_mp(this, &ThemeTypeDialog::_add_type_dialog_entered));
+ add_type_filter->connect(SceneStringName(gui_input), callable_mp(this, &ThemeTypeDialog::_type_filter_input));
Label *add_type_options_label = memnew(Label);
add_type_options_label->set_text(TTR("Available Node-based types:"));
diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h
index ba8e3a30b7..ae92365c32 100644
--- a/editor/plugins/theme_editor_plugin.h
+++ b/editor/plugins/theme_editor_plugin.h
@@ -303,6 +303,7 @@ class ThemeTypeDialog : public ConfirmationDialog {
void _update_add_type_options(const String &p_filter = "");
void _add_type_filter_cbk(const String &p_value);
+ void _type_filter_input(const Ref<InputEvent> &p_ie);
void _add_type_options_cbk(int p_index);
void _add_type_dialog_entered(const String &p_value);
void _add_type_dialog_activated(int p_index);
diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp
index af52243c41..8dbf58e228 100644
--- a/editor/plugins/tiles/tile_data_editors.cpp
+++ b/editor/plugins/tiles/tile_data_editors.cpp
@@ -917,7 +917,7 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() {
button_expand->set_toggle_mode(true);
button_expand->set_pressed(false);
button_expand->set_tooltip_text(TTR("Expand editor"));
- button_expand->connect("toggled", callable_mp(this, &GenericTilePolygonEditor::_toggle_expand));
+ button_expand->connect(SceneStringName(toggled), callable_mp(this, &GenericTilePolygonEditor::_toggle_expand));
toolbar->add_child(button_expand);
toolbar->add_child(memnew(VSeparator));
diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp
index 63a54372b5..7e6746dd3c 100644
--- a/editor/plugins/tiles/tile_map_layer_editor.cpp
+++ b/editor/plugins/tiles/tile_map_layer_editor.cpp
@@ -86,8 +86,7 @@ void TileMapLayerEditorTilesPlugin::_update_toolbar() {
transform_toolbar->show();
tools_settings_vsep_2->show();
random_tile_toggle->show();
- scatter_label->show();
- scatter_spinbox->show();
+ scatter_controls_container->set_visible(random_tile_toggle->is_pressed());
} else {
tools_settings_vsep->show();
picker_button->show();
@@ -96,8 +95,7 @@ void TileMapLayerEditorTilesPlugin::_update_toolbar() {
tools_settings_vsep_2->show();
bucket_contiguous_checkbox->show();
random_tile_toggle->show();
- scatter_label->show();
- scatter_spinbox->show();
+ scatter_controls_container->set_visible(random_tile_toggle->is_pressed());
}
}
@@ -2330,7 +2328,7 @@ TileMapLayerEditorTilesPlugin::TileMapLayerEditorTilesPlugin() {
random_tile_toggle->set_theme_type_variation("FlatButton");
random_tile_toggle->set_toggle_mode(true);
random_tile_toggle->set_tooltip_text(TTR("Place Random Tile"));
- random_tile_toggle->connect("toggled", callable_mp(this, &TileMapLayerEditorTilesPlugin::_on_random_tile_checkbox_toggled));
+ random_tile_toggle->connect(SceneStringName(toggled), callable_mp(this, &TileMapLayerEditorTilesPlugin::_on_random_tile_checkbox_toggled));
tools_settings->add_child(random_tile_toggle);
// Random tile scattering.
@@ -4486,7 +4484,7 @@ TileMapLayerEditor::TileMapLayerEditor() {
toggle_highlight_selected_layer_button->set_theme_type_variation("FlatButton");
toggle_highlight_selected_layer_button->set_toggle_mode(true);
toggle_highlight_selected_layer_button->set_pressed(true);
- toggle_highlight_selected_layer_button->connect("toggled", callable_mp(this, &TileMapLayerEditor::_highlight_selected_layer_button_toggled));
+ toggle_highlight_selected_layer_button->connect(SceneStringName(toggled), callable_mp(this, &TileMapLayerEditor::_highlight_selected_layer_button_toggled));
toggle_highlight_selected_layer_button->set_tooltip_text(TTR("Highlight Selected TileMap Layer"));
tile_map_toolbar->add_child(toggle_highlight_selected_layer_button);
@@ -4497,7 +4495,7 @@ TileMapLayerEditor::TileMapLayerEditor() {
toggle_grid_button->set_theme_type_variation("FlatButton");
toggle_grid_button->set_toggle_mode(true);
toggle_grid_button->set_tooltip_text(TTR("Toggle grid visibility."));
- toggle_grid_button->connect("toggled", callable_mp(this, &TileMapLayerEditor::_on_grid_toggled));
+ toggle_grid_button->connect(SceneStringName(toggled), callable_mp(this, &TileMapLayerEditor::_on_grid_toggled));
tile_map_toolbar->add_child(toggle_grid_button);
// Advanced settings menu button.
diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp
index 071be13692..1df84cb0a7 100644
--- a/editor/plugins/version_control_editor_plugin.cpp
+++ b/editor/plugins/version_control_editor_plugin.cpp
@@ -1006,7 +1006,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() {
toggle_vcs_choice = memnew(CheckButton);
toggle_vcs_choice->set_h_size_flags(Control::SIZE_EXPAND_FILL);
toggle_vcs_choice->set_pressed_no_signal(false);
- toggle_vcs_choice->connect(SNAME("toggled"), callable_mp(this, &VersionControlEditorPlugin::_toggle_vcs_integration));
+ toggle_vcs_choice->connect(SceneStringName(toggled), callable_mp(this, &VersionControlEditorPlugin::_toggle_vcs_integration));
toggle_vcs_hbc->add_child(toggle_vcs_choice);
set_up_vbc->add_child(memnew(HSeparator));
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index c183aceb13..3059d10c4c 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -1590,7 +1590,7 @@ void VisualShaderEditor::clear_custom_types() {
}
void VisualShaderEditor::add_custom_type(const String &p_name, const String &p_type, const Ref<Script> &p_script, const String &p_description, int p_return_icon_type, const String &p_category, bool p_highend) {
- ERR_FAIL_COND(!p_name.is_valid_identifier());
+ ERR_FAIL_COND(!p_name.is_valid_ascii_identifier());
ERR_FAIL_COND(p_type.is_empty() && !p_script.is_valid());
for (int i = 0; i < add_options.size(); i++) {
@@ -3667,6 +3667,7 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, cons
bool is_curve = (Object::cast_to<VisualShaderNodeCurveTexture>(vsnode.ptr()) != nullptr);
bool is_curve_xyz = (Object::cast_to<VisualShaderNodeCurveXYZTexture>(vsnode.ptr()) != nullptr);
bool is_parameter = (Object::cast_to<VisualShaderNodeParameter>(vsnode.ptr()) != nullptr);
+ bool is_mesh_emitter = (Object::cast_to<VisualShaderNodeParticleMeshEmitter>(vsnode.ptr()) != nullptr);
Point2 position = graph->get_scroll_offset();
@@ -3879,6 +3880,12 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, cons
if (is_texture2d_array) {
undo_redo->force_fixed_history();
undo_redo->add_do_method(vsnode.ptr(), "set_texture_array", ResourceLoader::load(p_resource_path));
+ return;
+ }
+
+ if (is_mesh_emitter) {
+ undo_redo->add_do_method(vsnode.ptr(), "set_mesh", ResourceLoader::load(p_resource_path));
+ return;
}
}
}
@@ -5791,7 +5798,7 @@ void VisualShaderEditor::_varying_create() {
}
void VisualShaderEditor::_varying_name_changed(const String &p_name) {
- if (!p_name.is_valid_identifier()) {
+ if (!p_name.is_valid_ascii_identifier()) {
varying_error_label->show();
varying_error_label->set_text(TTR("Invalid name for varying."));
add_varying_dialog->get_ok_button()->set_disabled(true);
@@ -6062,6 +6069,11 @@ void VisualShaderEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE);
saved_node_pos_dirty = true;
_add_node(cubemap_node_option_idx, {}, arr[i], i);
+ } else if (type == "Mesh" && visual_shader->get_mode() == Shader::MODE_PARTICLES &&
+ (visual_shader->get_shader_type() == VisualShader::TYPE_START || visual_shader->get_shader_type() == VisualShader::TYPE_START_CUSTOM)) {
+ saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE);
+ saved_node_pos_dirty = true;
+ _add_node(mesh_emitter_option_idx, {}, arr[i], i);
}
}
}
@@ -6370,7 +6382,7 @@ VisualShaderEditor::VisualShaderEditor() {
custom_mode_box->set_text(TTR("Custom"));
custom_mode_box->set_pressed(false);
custom_mode_box->set_visible(false);
- custom_mode_box->connect("toggled", callable_mp(this, &VisualShaderEditor::_custom_mode_toggled));
+ custom_mode_box->connect(SceneStringName(toggled), callable_mp(this, &VisualShaderEditor::_custom_mode_toggled));
edit_type_standard = memnew(OptionButton);
edit_type_standard->add_item(TTR("Vertex"));
@@ -6807,6 +6819,7 @@ VisualShaderEditor::VisualShaderEditor() {
// NODE3D-FOR-ALL
+ add_options.push_back(AddOption("ClipSpaceFar", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "clip_space_far", "CLIP_SPACE_FAR"), { "clip_space_far" }, VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_SPATIAL));
add_options.push_back(AddOption("Exposure", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "exposure", "EXPOSURE"), { "exposure" }, VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_SPATIAL));
add_options.push_back(AddOption("InvProjectionMatrix", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_projection_matrix", "INV_PROJECTION_MATRIX"), { "inv_projection_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL));
add_options.push_back(AddOption("InvViewMatrix", "Input/All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_view_matrix", "INV_VIEW_MATRIX"), { "inv_view_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL));
@@ -7012,7 +7025,10 @@ VisualShaderEditor::VisualShaderEditor() {
add_options.push_back(AddOption("MultiplyByAxisAngle (*)", "Particles/Transform", "VisualShaderNodeParticleMultiplyByAxisAngle", TTR("A node for help to multiply a position input vector by rotation using specific axis. Intended to work with emitters."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES));
add_options.push_back(AddOption("BoxEmitter", "Particles/Emitters", "VisualShaderNodeParticleBoxEmitter", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
+
+ mesh_emitter_option_idx = add_options.size();
add_options.push_back(AddOption("MeshEmitter", "Particles/Emitters", "VisualShaderNodeParticleMeshEmitter", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
+
add_options.push_back(AddOption("RingEmitter", "Particles/Emitters", "VisualShaderNodeParticleRingEmitter", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
add_options.push_back(AddOption("SphereEmitter", "Particles/Emitters", "VisualShaderNodeParticleSphereEmitter", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES));
@@ -7978,7 +7994,7 @@ void VisualShaderNodePortPreview::_shader_changed() {
preview_shader->set_code(shader_code);
for (int i = 0; i < default_textures.size(); i++) {
int j = 0;
- for (List<Ref<Texture2D>>::ConstIterator itr = default_textures[i].params.begin(); itr != default_textures[i].params.end(); ++itr, ++j) {
+ for (List<Ref<Texture>>::ConstIterator itr = default_textures[i].params.begin(); itr != default_textures[i].params.end(); ++itr, ++j) {
preview_shader->set_default_texture_parameter(default_textures[i].name, *itr, j);
}
}
diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h
index b17036e39f..69b2f30c40 100644
--- a/editor/plugins/visual_shader_editor_plugin.h
+++ b/editor/plugins/visual_shader_editor_plugin.h
@@ -414,6 +414,7 @@ class VisualShaderEditor : public ShaderEditor {
int custom_node_option_idx;
int curve_node_option_idx;
int curve_xyz_node_option_idx;
+ int mesh_emitter_option_idx;
List<String> keyword_list;
List<VisualShaderNodeParameterRef> uniform_refs;
diff --git a/editor/progress_dialog.cpp b/editor/progress_dialog.cpp
index c21723d1ba..2f345e5161 100644
--- a/editor/progress_dialog.cpp
+++ b/editor/progress_dialog.cpp
@@ -202,14 +202,13 @@ void ProgressDialog::add_task(const String &p_task, const String &p_label, int p
bool ProgressDialog::task_step(const String &p_task, const String &p_state, int p_step, bool p_force_redraw) {
ERR_FAIL_COND_V(!tasks.has(p_task), canceled);
+ Task &t = tasks[p_task];
if (!p_force_redraw) {
uint64_t tus = OS::get_singleton()->get_ticks_usec();
- if (tus - last_progress_tick < 200000) { //200ms
+ if (tus - t.last_progress_tick < 200000) { //200ms
return canceled;
}
}
-
- Task &t = tasks[p_task];
if (p_step < 0) {
t.progress->set_value(t.progress->get_value() + 1);
} else {
@@ -217,7 +216,7 @@ bool ProgressDialog::task_step(const String &p_task, const String &p_state, int
}
t.state->set_text(p_state);
- last_progress_tick = OS::get_singleton()->get_ticks_usec();
+ t.last_progress_tick = OS::get_singleton()->get_ticks_usec();
_update_ui();
return canceled;
@@ -252,7 +251,6 @@ ProgressDialog::ProgressDialog() {
main->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
set_exclusive(true);
set_flag(Window::FLAG_POPUP, false);
- last_progress_tick = 0;
singleton = this;
cancel_hb = memnew(HBoxContainer);
main->add_child(cancel_hb);
diff --git a/editor/progress_dialog.h b/editor/progress_dialog.h
index 82d59219da..355812b0b7 100644
--- a/editor/progress_dialog.h
+++ b/editor/progress_dialog.h
@@ -71,13 +71,13 @@ class ProgressDialog : public PopupPanel {
VBoxContainer *vb = nullptr;
ProgressBar *progress = nullptr;
Label *state = nullptr;
+ uint64_t last_progress_tick = 0;
};
HBoxContainer *cancel_hb = nullptr;
Button *cancel = nullptr;
HashMap<String, Task> tasks;
VBoxContainer *main = nullptr;
- uint64_t last_progress_tick;
LocalVector<Window *> host_windows;
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index 3de9ebcfdd..55c361de4b 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -93,6 +93,7 @@ void ProjectManager::_notification(int p_what) {
} break;
case NOTIFICATION_READY: {
+ DisplayServer::get_singleton()->screen_set_keep_on(EDITOR_GET("interface/editor/keep_screen_on"));
const int default_sorting = (int)EDITOR_GET("project_manager/sorting_order");
filter_option->select(default_sorting);
project_list->set_order_option(default_sorting);
diff --git a/editor/project_manager/project_dialog.cpp b/editor/project_manager/project_dialog.cpp
index 28d2362c9c..9b009c0a89 100644
--- a/editor/project_manager/project_dialog.cpp
+++ b/editor/project_manager/project_dialog.cpp
@@ -857,7 +857,7 @@ ProjectDialog::ProjectDialog() {
create_dir->set_text(TTR("Create Folder"));
create_dir->set_pressed(true);
pphb_label->add_child(create_dir);
- create_dir->connect("toggled", callable_mp(this, &ProjectDialog::_create_dir_toggled));
+ create_dir->connect(SceneStringName(toggled), callable_mp(this, &ProjectDialog::_create_dir_toggled));
HBoxContainer *pphb = memnew(HBoxContainer);
project_path_container->add_child(pphb);
diff --git a/editor/project_manager/project_list.cpp b/editor/project_manager/project_list.cpp
index 092a6a1a18..541ab01e62 100644
--- a/editor/project_manager/project_list.cpp
+++ b/editor/project_manager/project_list.cpp
@@ -760,7 +760,7 @@ void ProjectList::_create_project_item_control(int p_index) {
hb->set_tags(item.tags, this);
hb->set_unsupported_features(item.unsupported_features.duplicate());
hb->set_project_version(item.project_version);
- hb->set_last_edited_info(Time::get_singleton()->get_datetime_string_from_unix_time(item.last_edited, true));
+ hb->set_last_edited_info(!item.missing ? Time::get_singleton()->get_datetime_string_from_unix_time(item.last_edited, true) : TTR("Missing Date"));
hb->set_is_favorite(item.favorite);
hb->set_is_missing(item.missing);
diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp
index bdf4e41c5f..489fbb037f 100644
--- a/editor/project_settings_editor.cpp
+++ b/editor/project_settings_editor.cpp
@@ -221,7 +221,7 @@ void ProjectSettingsEditor::_update_property_box() {
const Vector<String> names = name.split("/");
for (int i = 0; i < names.size(); i++) {
- if (!names[i].is_valid_identifier()) {
+ if (!names[i].is_valid_ascii_identifier()) {
return;
}
}
@@ -652,7 +652,7 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) {
advanced = memnew(CheckButton);
advanced->set_text(TTR("Advanced Settings"));
- advanced->connect("toggled", callable_mp(this, &ProjectSettingsEditor::_advanced_toggled));
+ advanced->connect(SceneStringName(toggled), callable_mp(this, &ProjectSettingsEditor::_advanced_toggled));
search_bar->add_child(advanced);
custom_properties = memnew(HBoxContainer);
diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp
index 610ad3efdf..8e135e7eae 100644
--- a/editor/register_editor_types.cpp
+++ b/editor/register_editor_types.cpp
@@ -46,6 +46,7 @@
#include "editor/editor_translation_parser.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/export/editor_export_platform.h"
+#include "editor/export/editor_export_platform_extension.h"
#include "editor/export/editor_export_platform_pc.h"
#include "editor/export/editor_export_plugin.h"
#include "editor/filesystem_dock.h"
@@ -161,6 +162,8 @@ void register_editor_types() {
GDREGISTER_CLASS(EditorExportPlugin);
GDREGISTER_ABSTRACT_CLASS(EditorExportPlatform);
GDREGISTER_ABSTRACT_CLASS(EditorExportPlatformPC);
+ GDREGISTER_CLASS(EditorExportPlatformExtension);
+ GDREGISTER_ABSTRACT_CLASS(EditorExportPreset);
register_exporter_types();
diff --git a/editor/rename_dialog.cpp b/editor/rename_dialog.cpp
index 71d2ccf124..2ef7de1538 100644
--- a/editor/rename_dialog.cpp
+++ b/editor/rename_dialog.cpp
@@ -304,7 +304,7 @@ RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor) {
// ---- Connections
- cbut_collapse_features->connect("toggled", callable_mp(this, &RenameDialog::_features_toggled));
+ cbut_collapse_features->connect(SceneStringName(toggled), callable_mp(this, &RenameDialog::_features_toggled));
// Substitute Buttons
diff --git a/editor/run_instances_dialog.cpp b/editor/run_instances_dialog.cpp
index d617c899ad..bb32748653 100644
--- a/editor/run_instances_dialog.cpp
+++ b/editor/run_instances_dialog.cpp
@@ -304,7 +304,7 @@ RunInstancesDialog::RunInstancesDialog() {
args_gc->add_child(instance_count);
instance_count->connect(SceneStringName(value_changed), callable_mp(this, &RunInstancesDialog::_start_instance_timer).unbind(1));
instance_count->connect(SceneStringName(value_changed), callable_mp(this, &RunInstancesDialog::_refresh_argument_count).unbind(1));
- enable_multiple_instances_checkbox->connect("toggled", callable_mp(instance_count, &SpinBox::set_editable));
+ enable_multiple_instances_checkbox->connect(SceneStringName(toggled), callable_mp(instance_count, &SpinBox::set_editable));
instance_count->set_editable(enable_multiple_instances_checkbox->is_pressed());
main_args_edit = memnew(LineEdit);
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 30050901d9..3110ecb926 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -1181,7 +1181,16 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
case TOOL_OPEN_DOCUMENTATION: {
List<Node *> selection = editor_selection->get_selected_node_list();
for (const Node *node : selection) {
- ScriptEditor::get_singleton()->goto_help("class_name:" + node->get_class());
+ String class_name;
+ Ref<Script> script_base = node->get_script();
+ if (script_base.is_valid()) {
+ class_name = script_base->get_global_name();
+ }
+ if (class_name.is_empty()) {
+ class_name = node->get_class();
+ }
+
+ ScriptEditor::get_singleton()->goto_help("class_name:" + class_name);
}
EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
} break;
@@ -2641,6 +2650,13 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
}
}
+ if (!entire_scene) {
+ for (const Node *E : remove_list) {
+ // `move_child` + `get_index` doesn't really work for internal nodes.
+ ERR_FAIL_COND_MSG(E->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to remove internal node, this is not supported.");
+ }
+ }
+
EditorNode::get_singleton()->hide_unused_editors(this);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
@@ -2653,10 +2669,6 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
undo_redo->add_undo_method(scene_tree, "update_tree");
undo_redo->add_undo_reference(edited_scene);
} else {
- for (const Node *E : remove_list) {
- // `move_child` + `get_index` doesn't really work for internal nodes.
- ERR_FAIL_COND_MSG(E->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to remove internal node, this is not supported.");
- }
if (delete_tracks_checkbox->is_pressed() || p_cut) {
remove_list.sort_custom<Node::Comparator>(); // Sort nodes to keep positions.
HashMap<Node *, NodePath> path_renames;
diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp
index 64c97e1641..f8673ddd44 100644
--- a/editor/script_create_dialog.cpp
+++ b/editor/script_create_dialog.cpp
@@ -100,7 +100,7 @@ static Vector<String> _get_hierarchy(const String &p_class_name) {
}
if (hierarchy.is_empty()) {
- if (p_class_name.is_valid_identifier()) {
+ if (p_class_name.is_valid_ascii_identifier()) {
hierarchy.push_back(p_class_name);
}
hierarchy.push_back("Object");
diff --git a/editor/shader_create_dialog.cpp b/editor/shader_create_dialog.cpp
index fd9d5bc127..846e8867a1 100644
--- a/editor/shader_create_dialog.cpp
+++ b/editor/shader_create_dialog.cpp
@@ -629,7 +629,7 @@ ShaderCreateDialog::ShaderCreateDialog() {
internal = memnew(CheckBox);
internal->set_text(TTR("On"));
- internal->connect("toggled", callable_mp(this, &ShaderCreateDialog::_built_in_toggled));
+ internal->connect(SceneStringName(toggled), callable_mp(this, &ShaderCreateDialog::_built_in_toggled));
gc->add_child(memnew(Label(TTR("Built-in Shader:"))));
gc->add_child(internal);
diff --git a/editor/shader_globals_editor.cpp b/editor/shader_globals_editor.cpp
index 0582c598a1..c05f60545d 100644
--- a/editor/shader_globals_editor.cpp
+++ b/editor/shader_globals_editor.cpp
@@ -349,7 +349,7 @@ String ShaderGlobalsEditor::_check_new_variable_name(const String &p_variable_na
return TTR("Name cannot be empty.");
}
- if (!p_variable_name.is_valid_identifier()) {
+ if (!p_variable_name.is_valid_ascii_identifier()) {
return TTR("Name must be a valid identifier.");
}
diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index 9237a62a74..f5a790353a 100644
--- a/editor/themes/editor_theme_manager.cpp
+++ b/editor/themes/editor_theme_manager.cpp
@@ -1471,8 +1471,44 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
}
// SpinBox.
- p_theme->set_icon("updown", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxUpdown"), EditorStringName(EditorIcons)));
- p_theme->set_icon("updown_disabled", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxUpdownDisabled"), EditorStringName(EditorIcons)));
+ {
+ Ref<Texture2D> empty_icon = memnew(ImageTexture);
+ p_theme->set_icon("updown", "SpinBox", empty_icon);
+ p_theme->set_icon("up", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxUp"), EditorStringName(EditorIcons)));
+ p_theme->set_icon("up_hover", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxUp"), EditorStringName(EditorIcons)));
+ p_theme->set_icon("up_pressed", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxUp"), EditorStringName(EditorIcons)));
+ p_theme->set_icon("up_disabled", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxUp"), EditorStringName(EditorIcons)));
+ p_theme->set_icon("down", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxDown"), EditorStringName(EditorIcons)));
+ p_theme->set_icon("down_hover", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxDown"), EditorStringName(EditorIcons)));
+ p_theme->set_icon("down_pressed", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxDown"), EditorStringName(EditorIcons)));
+ p_theme->set_icon("down_disabled", "SpinBox", p_theme->get_icon(SNAME("GuiSpinboxDown"), EditorStringName(EditorIcons)));
+
+ p_theme->set_stylebox("up_background", "SpinBox", make_empty_stylebox());
+ p_theme->set_stylebox("up_background_hovered", "SpinBox", p_config.button_style_hover);
+ p_theme->set_stylebox("up_background_pressed", "SpinBox", p_config.button_style_pressed);
+ p_theme->set_stylebox("up_background_disabled", "SpinBox", make_empty_stylebox());
+ p_theme->set_stylebox("down_background", "SpinBox", make_empty_stylebox());
+ p_theme->set_stylebox("down_background_hovered", "SpinBox", p_config.button_style_hover);
+ p_theme->set_stylebox("down_background_pressed", "SpinBox", p_config.button_style_pressed);
+ p_theme->set_stylebox("down_background_disabled", "SpinBox", make_empty_stylebox());
+
+ p_theme->set_color("up_icon_modulate", "SpinBox", p_config.font_color);
+ p_theme->set_color("up_hover_icon_modulate", "SpinBox", p_config.font_hover_color);
+ p_theme->set_color("up_pressed_icon_modulate", "SpinBox", p_config.font_pressed_color);
+ p_theme->set_color("up_disabled_icon_modulate", "SpinBox", p_config.font_disabled_color);
+ p_theme->set_color("down_icon_modulate", "SpinBox", p_config.font_color);
+ p_theme->set_color("down_hover_icon_modulate", "SpinBox", p_config.font_hover_color);
+ p_theme->set_color("down_pressed_icon_modulate", "SpinBox", p_config.font_pressed_color);
+ p_theme->set_color("down_disabled_icon_modulate", "SpinBox", p_config.font_disabled_color);
+
+ p_theme->set_stylebox("field_and_buttons_separator", "SpinBox", make_empty_stylebox());
+ p_theme->set_stylebox("up_down_buttons_separator", "SpinBox", make_empty_stylebox());
+
+ p_theme->set_constant("buttons_vertical_separation", "SpinBox", 0);
+ p_theme->set_constant("field_and_buttons_separation", "SpinBox", 2);
+ p_theme->set_constant("buttons_width", "SpinBox", 16);
+ p_theme->set_constant("set_min_buttons_width_from_icons", "SpinBox", 1);
+ }
// ProgressBar.
p_theme->set_stylebox("background", "ProgressBar", make_stylebox(p_theme->get_icon(SNAME("GuiProgressBar"), EditorStringName(EditorIcons)), 4, 4, 4, 4, 0, 0, 0, 0));
@@ -1768,7 +1804,9 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme
p_theme->set_color("background", EditorStringName(Editor), background_color_opaque);
p_theme->set_stylebox("Background", EditorStringName(EditorStyles), make_flat_stylebox(background_color_opaque, p_config.base_margin, p_config.base_margin, p_config.base_margin, p_config.base_margin));
- p_theme->set_stylebox("PanelForeground", EditorStringName(EditorStyles), p_config.base_style);
+ Ref<StyleBoxFlat> editor_panel_foreground = p_config.base_style->duplicate();
+ editor_panel_foreground->set_corner_radius_all(0);
+ p_theme->set_stylebox("PanelForeground", EditorStringName(EditorStyles), editor_panel_foreground);
// Editor focus.
p_theme->set_stylebox("Focus", EditorStringName(EditorStyles), p_config.button_style_focus);
@@ -1858,6 +1896,10 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme
editor_spin_label_bg->set_border_width_all(0);
p_theme->set_stylebox("label_bg", "EditorSpinSlider", editor_spin_label_bg);
+ // TODO Use separate arrows instead like on SpinBox. Planned for a different PR.
+ p_theme->set_icon("updown", "EditorSpinSlider", p_theme->get_icon(SNAME("GuiSpinboxUpdown"), EditorStringName(EditorIcons)));
+ p_theme->set_icon("updown_disabled", "EditorSpinSlider", p_theme->get_icon(SNAME("GuiSpinboxUpdownDisabled"), EditorStringName(EditorIcons)));
+
// Launch Pad and Play buttons.
Ref<StyleBoxFlat> style_launch_pad = make_flat_stylebox(p_config.dark_color_1, 2 * EDSCALE, 0, 2 * EDSCALE, 0, p_config.corner_radius);
style_launch_pad->set_corner_radius_all(p_config.corner_radius * EDSCALE);
diff --git a/gles3_builders.py b/gles3_builders.py
index a4928c81c5..a81d42b42e 100644
--- a/gles3_builders.py
+++ b/gles3_builders.py
@@ -3,7 +3,7 @@
import os.path
from typing import Optional
-from methods import print_error
+from methods import print_error, to_raw_cstring
class GLES3HeaderStruct:
@@ -553,20 +553,12 @@ def build_gles3_header(
fd.write("\t\tstatic const Feedback* _feedbacks=nullptr;\n")
fd.write("\t\tstatic const char _vertex_code[]={\n")
- for x in header_data.vertex_lines:
- for c in x:
- fd.write(str(ord(c)) + ",")
-
- fd.write(str(ord("\n")) + ",")
- fd.write("\t\t0};\n\n")
+ fd.write(to_raw_cstring(header_data.vertex_lines))
+ fd.write("\n\t\t};\n\n")
fd.write("\t\tstatic const char _fragment_code[]={\n")
- for x in header_data.fragment_lines:
- for c in x:
- fd.write(str(ord(c)) + ",")
-
- fd.write(str(ord("\n")) + ",")
- fd.write("\t\t0};\n\n")
+ fd.write(to_raw_cstring(header_data.fragment_lines))
+ fd.write("\n\t\t};\n\n")
fd.write(
'\t\t_setup(_vertex_code,_fragment_code,"'
diff --git a/glsl_builders.py b/glsl_builders.py
index 05aab3acbb..82c15fc93b 100644
--- a/glsl_builders.py
+++ b/glsl_builders.py
@@ -1,25 +1,9 @@
"""Functions used to generate source files during build time"""
import os.path
-from typing import Iterable, Optional
+from typing import Optional
-from methods import print_error
-
-
-def generate_inline_code(input_lines: Iterable[str], insert_newline: bool = True):
- """Take header data and generate inline code
-
- :param: input_lines: values for shared inline code
- :return: str - generated inline value
- """
- output = []
- for line in input_lines:
- if line:
- output.append(",".join(str(ord(c)) for c in line))
- if insert_newline:
- output.append("%s" % ord("\n"))
- output.append("0")
- return ",".join(output)
+from methods import print_error, to_raw_cstring
class RDHeaderStruct:
@@ -127,13 +111,13 @@ def build_rd_header(
if header_data.compute_lines:
body_parts = [
- "static const char _compute_code[] = {\n%s\n\t\t};" % generate_inline_code(header_data.compute_lines),
+ "static const char _compute_code[] = {\n%s\n\t\t};" % to_raw_cstring(header_data.compute_lines),
f'setup(nullptr, nullptr, _compute_code, "{out_file_class}");',
]
else:
body_parts = [
- "static const char _vertex_code[] = {\n%s\n\t\t};" % generate_inline_code(header_data.vertex_lines),
- "static const char _fragment_code[] = {\n%s\n\t\t};" % generate_inline_code(header_data.fragment_lines),
+ "static const char _vertex_code[] = {\n%s\n\t\t};" % to_raw_cstring(header_data.vertex_lines),
+ "static const char _fragment_code[] = {\n%s\n\t\t};" % to_raw_cstring(header_data.fragment_lines),
f'setup(_vertex_code, _fragment_code, nullptr, "{out_file_class}");',
]
@@ -211,7 +195,7 @@ def build_raw_header(
#define {out_file_ifdef}_RAW_H
static const char {out_file_base}[] = {{
- {generate_inline_code(header_data.code, insert_newline=False)}
+{to_raw_cstring(header_data.code)}
}};
#endif
"""
diff --git a/godot.manifest b/godot.manifest
index 30b80aff25..17b588cafd 100644
--- a/godot.manifest
+++ b/godot.manifest
@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">
+ <windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
+ <ws2:longPathAware>true</ws2:longPathAware>
+ </windowsSettings>
+ </application>
<dependency>
<dependentAssembly>
<assemblyIdentity
diff --git a/main/main.cpp b/main/main.cpp
index d76ddd5a66..101fc642b6 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -2555,6 +2555,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF_BASIC("xr/openxr/startup_alert", true);
// OpenXR project extensions settings.
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/extensions/debug_utils", PROPERTY_HINT_ENUM, "Disabled,Error,Warning,Info,Verbose"), "0");
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "xr/openxr/extensions/debug_message_types", PROPERTY_HINT_FLAGS, "General,Validation,Performance,Conformance"), "15");
GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking", false);
GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking_unobstructed_data_source", false); // XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT
GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking_controller_data_source", false); // XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT
@@ -3130,7 +3132,7 @@ Error Main::setup2(bool p_show_boot_logo) {
DisplayServer::set_early_window_clear_color_override(false);
- GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/config/icon", PROPERTY_HINT_FILE, "*.png,*.webp,*.svg"), String());
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/config/icon", PROPERTY_HINT_FILE, "*.png,*.bmp,*.hdr,*.jpg,*.jpeg,*.svg,*.tga,*.exr,*.webp"), String());
GLOBAL_DEF(PropertyInfo(Variant::STRING, "application/config/macos_native_icon", PROPERTY_HINT_FILE, "*.icns"), String());
GLOBAL_DEF(PropertyInfo(Variant::STRING, "application/config/windows_native_icon", PROPERTY_HINT_FILE, "*.ico"), String());
@@ -3299,7 +3301,7 @@ Error Main::setup2(bool p_show_boot_logo) {
OS::get_singleton()->benchmark_end_measure("Startup", "Platforms");
- GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/mouse_cursor/custom_image", PROPERTY_HINT_FILE, "*.png,*.webp"), String());
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/mouse_cursor/custom_image", PROPERTY_HINT_FILE, "*.png,*.bmp,*.hdr,*.jpg,*.jpeg,*.svg,*.tga,*.exr,*.webp"), String());
GLOBAL_DEF_BASIC("display/mouse_cursor/custom_image_hotspot", Vector2());
GLOBAL_DEF_BASIC("display/mouse_cursor/tooltip_position_offset", Point2(10, 10));
@@ -3392,7 +3394,8 @@ void Main::setup_boot_logo() {
boot_logo.instantiate();
Error load_err = ImageLoader::load_image(boot_logo_path, boot_logo);
if (load_err) {
- ERR_PRINT("Non-existing or invalid boot splash at '" + boot_logo_path + "'. Loading default splash.");
+ String msg = (boot_logo_path.ends_with(".png") ? "" : "The only supported format is PNG.");
+ ERR_PRINT("Non-existing or invalid boot splash at '" + boot_logo_path + +"'. " + msg + " Loading default splash.");
}
}
} else {
diff --git a/methods.py b/methods.py
index c725501fd9..bfd08cfc7b 100644
--- a/methods.py
+++ b/methods.py
@@ -8,7 +8,7 @@ from collections import OrderedDict
from enum import Enum
from io import StringIO, TextIOWrapper
from pathlib import Path
-from typing import Generator, Optional
+from typing import Generator, List, Optional, Union
# Get the "Godot" folder name ahead of time
base_folder_path = str(os.path.abspath(Path(__file__).parent)) + "/"
@@ -163,7 +163,7 @@ def add_source_files(self, sources, files, allow_gen=False):
def disable_warnings(self):
# 'self' is the environment
- if self.msvc:
+ if self.msvc and not using_clang(self):
# We have to remove existing warning level defines before appending /w,
# otherwise we get: "warning D9025 : overriding '/W3' with '/w'"
self["CCFLAGS"] = [x for x in self["CCFLAGS"] if not (x.startswith("/W") or x.startswith("/w"))]
@@ -495,6 +495,8 @@ def use_windows_spawn_fix(self, platform=None):
rv = proc.wait()
if rv:
print_error(err)
+ elif len(err) > 0 and not err.isspace():
+ print(err)
return rv
def mySpawn(sh, escape, cmd, args, env):
@@ -815,21 +817,20 @@ def get_compiler_version(env):
"apple_patch3": -1,
}
- if not env.msvc:
- # Not using -dumpversion as some GCC distros only return major, and
- # Clang used to return hardcoded 4.2.1: # https://reviews.llvm.org/D56803
- try:
- version = (
- subprocess.check_output([env.subst(env["CXX"]), "--version"], shell=(os.name == "nt"))
- .strip()
- .decode("utf-8")
- )
- except (subprocess.CalledProcessError, OSError):
- print_warning("Couldn't parse CXX environment variable to infer compiler version.")
- return ret
- else:
+ if env.msvc and not using_clang(env):
# TODO: Implement for MSVC
return ret
+
+ # Not using -dumpversion as some GCC distros only return major, and
+ # Clang used to return hardcoded 4.2.1: # https://reviews.llvm.org/D56803
+ try:
+ version = subprocess.check_output(
+ [env.subst(env["CXX"]), "--version"], shell=(os.name == "nt"), encoding="utf-8"
+ ).strip()
+ except (subprocess.CalledProcessError, OSError):
+ print_warning("Couldn't parse CXX environment variable to infer compiler version.")
+ return ret
+
match = re.search(
r"(?:(?<=version )|(?<=\) )|(?<=^))"
r"(?P<major>\d+)"
@@ -1641,3 +1642,43 @@ def generated_wrapper(
file.write(f"\n\n#endif // {header_guard}")
file.write("\n")
+
+
+def to_raw_cstring(value: Union[str, List[str]]) -> str:
+ MAX_LITERAL = 16 * 1024
+
+ if isinstance(value, list):
+ value = "\n".join(value) + "\n"
+
+ split: List[bytes] = []
+ offset = 0
+ encoded = value.encode()
+
+ while offset <= len(encoded):
+ segment = encoded[offset : offset + MAX_LITERAL]
+ offset += MAX_LITERAL
+ if len(segment) == MAX_LITERAL:
+ # Try to segment raw strings at double newlines to keep readable.
+ pretty_break = segment.rfind(b"\n\n")
+ if pretty_break != -1:
+ segment = segment[: pretty_break + 1]
+ offset -= MAX_LITERAL - pretty_break - 1
+ # If none found, ensure we end with valid utf8.
+ # https://github.com/halloleo/unicut/blob/master/truncate.py
+ elif segment[-1] & 0b10000000:
+ last_11xxxxxx_index = [i for i in range(-1, -5, -1) if segment[i] & 0b11000000 == 0b11000000][0]
+ last_11xxxxxx = segment[last_11xxxxxx_index]
+ if not last_11xxxxxx & 0b00100000:
+ last_char_length = 2
+ elif not last_11xxxxxx & 0b0010000:
+ last_char_length = 3
+ elif not last_11xxxxxx & 0b0001000:
+ last_char_length = 4
+
+ if last_char_length > -last_11xxxxxx_index:
+ segment = segment[:last_11xxxxxx_index]
+ offset += last_11xxxxxx_index
+
+ split += [segment]
+
+ return " ".join(f'R"<!>({x.decode()})<!>"' for x in split)
diff --git a/misc/dist/linux/org.godotengine.Godot.desktop b/misc/dist/linux/org.godotengine.Godot.desktop
index d351839205..28669548d6 100644
--- a/misc/dist/linux/org.godotengine.Godot.desktop
+++ b/misc/dist/linux/org.godotengine.Godot.desktop
@@ -4,11 +4,13 @@ GenericName=Libre game engine
GenericName[el]=Ελεύθερη μηχανή παιχνιδιού
GenericName[fr]=Moteur de jeu libre
GenericName[nl]=Libre game-engine
+GenericName[ru]=Свободный игровой движок
GenericName[zh_CN]=自由的游戏引擎
Comment=Multi-platform 2D and 3D game engine with a feature-rich editor
Comment[el]=2D και 3D μηχανή παιχνιδιού πολλαπλών πλατφορμών με επεξεργαστή πλούσιο σε χαρακτηριστικά
Comment[fr]=Moteur de jeu 2D et 3D multiplateforme avec un éditeur riche en fonctionnalités
-Comment[nl]=Multi-platform 2D- en 3d-game-engine met een veelzijdige editor
+Comment[nl]=Multi-platform 2D- en 3D-game-engine met een veelzijdige editor
+Comment[ru]=Кроссплатформенный движок с многофункциональным редактором для 2D- и 3D-игр
Comment[zh_CN]=多平台 2D 和 3D 游戏引擎,带有功能丰富的编辑器
Exec=godot %f
Icon=godot
diff --git a/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected
index 733d85c46e..851ef69261 100644
--- a/misc/extension_api_validation/4.3-stable.expected
+++ b/misc/extension_api_validation/4.3-stable.expected
@@ -26,3 +26,42 @@ Validate extension JSON: Error: Field 'classes/RenderingDevice/methods/draw_list
draw_list_begin added a new optional debug argument called breadcrumb.
There used to be an Array argument as arg #9 initially, then changed to typedarray::RID in 4.1, and finally removed in 4.3.
Since we're adding a new one at the same location, we need to silence those warnings for 4.1 and 4.3.
+
+
+GH-95126
+--------
+Validate extension JSON: Error: Field 'classes/Shader/methods/get_default_texture_parameter/return_value': type changed value in new API, from "Texture2D" to "Texture".
+Validate extension JSON: Error: Field 'classes/Shader/methods/set_default_texture_parameter/arguments/1': type changed value in new API, from "Texture2D" to "Texture".
+Validate extension JSON: Error: Field 'classes/VisualShaderNodeCubemap/methods/get_cube_map/return_value': type changed value in new API, from "Cubemap" to "TextureLayered".
+Validate extension JSON: Error: Field 'classes/VisualShaderNodeCubemap/methods/set_cube_map/arguments/0': type changed value in new API, from "Cubemap" to "TextureLayered".
+Validate extension JSON: Error: Field 'classes/VisualShaderNodeCubemap/properties/cube_map': type changed value in new API, from "Cubemap" to "Cubemap,CompressedCubemap,PlaceholderCubemap,TextureCubemapRD".
+Validate extension JSON: Error: Field 'classes/VisualShaderNodeTexture2DArray/methods/get_texture_array/return_value': type changed value in new API, from "Texture2DArray" to "TextureLayered".
+Validate extension JSON: Error: Field 'classes/VisualShaderNodeTexture2DArray/methods/set_texture_array/arguments/0': type changed value in new API, from "Texture2DArray" to "TextureLayered".
+Validate extension JSON: Error: Field 'classes/VisualShaderNodeTexture2DArray/properties/texture_array': type changed value in new API, from "Texture2DArray" to "Texture2DArray,CompressedTexture2DArray,PlaceholderTexture2DArray,Texture2DArrayRD".
+
+Allow setting a cubemap as default parameter to shader.
+Compatibility methods registered.
+
+
+GH-93605
+--------
+Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/Semaphore/methods/post': arguments
+
+Optional arguments added. Compatibility methods registered.
+
+
+GH-95212
+--------
+Validate extension JSON: Error: Field 'classes/RegEx/methods/compile/arguments': size changed value in new API, from 1 to 2.
+Validate extension JSON: Error: Field 'classes/RegEx/methods/create_from_string/arguments': size changed value in new API, from 1 to 2.
+
+Add optional argument to control error printing on compilation fail. Compatibility methods registered.
+
+
+GH-95375
+--------
+Validate extension JSON: Error: Field 'classes/AudioStreamPlayer/properties/playing': setter changed value in new API, from "_set_playing" to &"set_playing".
+Validate extension JSON: Error: Field 'classes/AudioStreamPlayer2D/properties/playing': setter changed value in new API, from "_set_playing" to &"set_playing".
+Validate extension JSON: Error: Field 'classes/AudioStreamPlayer3D/properties/playing': setter changed value in new API, from "_set_playing" to &"set_playing".
+
+These setters have been renamed to expose them. GDExtension language bindings couldn't have exposed these properties before.
diff --git a/modules/betsy/bc6h.glsl b/modules/betsy/bc6h.glsl
index 0d10d378fd..37e7591aea 100644
--- a/modules/betsy/bc6h.glsl
+++ b/modules/betsy/bc6h.glsl
@@ -1,7 +1,7 @@
#[versions]
signed = "#define SIGNED";
-unsigned = "";
+unsigned = "#define QUALITY"; // The "Quality" preset causes artifacting on signed data, so for now it's exclusive to unsigned.
#[compute]
#version 450
@@ -10,10 +10,6 @@ unsigned = "";
#include "UavCrossPlatform_piece_all.glsl"
#VERSION_DEFINES
-#define QUALITY
-
-//SIGNED macro is WIP
-//#define SIGNED
float3 f32tof16(float3 value) {
return float3(packHalf2x16(float2(value.x, 0.0)),
@@ -48,11 +44,59 @@ params;
const float HALF_MAX = 65504.0f;
const uint PATTERN_NUM = 32u;
+#ifdef SIGNED
+const float HALF_MIN = -65504.0f;
+#else
+const float HALF_MIN = 0.0f;
+#endif
+
+#ifdef SIGNED
+// https://github.com/godotengine/godot/pull/96377#issuecomment-2323488254
+// https://github.com/godotengine/godot/pull/96377#issuecomment-2323450950
+bool isNegative(float a) {
+ return a < 0.0f;
+}
+
+float CalcSignlessMSLE(float a, float b) {
+ float err = log2((b + 1.0f) / (a + 1.0f));
+ err = err * err;
+ return err;
+}
+
+float CrossCalcMSLE(float a, float b) {
+ float result = 0.0f;
+ result += CalcSignlessMSLE(0.0f, abs(a));
+ result += CalcSignlessMSLE(0.0f, abs(b));
+ return result;
+}
+
+float CalcMSLE(float3 a, float3 b) {
+ float result = 0.0f;
+ if (isNegative(a.x) != isNegative(b.x)) {
+ result += CrossCalcMSLE(a.x, b.x);
+ } else {
+ result += CalcSignlessMSLE(abs(a.x), abs(b.x));
+ }
+ if (isNegative(a.y) != isNegative(b.y)) {
+ result += CrossCalcMSLE(a.y, b.y);
+ } else {
+ result += CalcSignlessMSLE(abs(a.y), abs(b.y));
+ }
+ if (isNegative(a.z) != isNegative(b.z)) {
+ result += CrossCalcMSLE(a.z, b.z);
+ } else {
+ result += CalcSignlessMSLE(abs(a.z), abs(b.z));
+ }
+
+ return result;
+}
+#else
float CalcMSLE(float3 a, float3 b) {
float3 err = log2((b + 1.0f) / (a + 1.0f));
err = err * err;
return err.x + err.y + err.z;
}
+#endif
uint PatternFixupID(uint i) {
uint ret = 15u;
@@ -176,11 +220,6 @@ float3 Unquantize10(float3 x) {
float3 FinishUnquantize(float3 endpoint0Unq, float3 endpoint1Unq, float weight) {
float3 comp = (endpoint0Unq * (64.0f - weight) + endpoint1Unq * weight + 32.0f) * (31.0f / 2048.0f);
- /*float3 signVal;
- signVal.x = comp.x >= 0.0f ? 0.0f : 0x8000;
- signVal.y = comp.y >= 0.0f ? 0.0f : 0x8000;
- signVal.z = comp.z >= 0.0f ? 0.0f : 0x8000;*/
- //return f16tof32( uint3( signVal + abs( comp ) ) );
return f16tof32(uint3(comp));
}
#endif
@@ -207,6 +246,7 @@ uint ComputeIndex4(float texelPos, float endPoint0Pos, float endPoint1Pos) {
return uint(clamp(r * 14.93333f + 0.03333f + 0.5f, 0.0f, 15.0f));
}
+// This adds a bitflag to quantized values that signifies whether they are negative.
void SignExtend(inout float3 v1, uint mask, uint signFlag) {
int3 v = int3(v1);
v.x = (v.x & int(mask)) | (v.x < 0 ? int(signFlag) : 0);
@@ -215,6 +255,7 @@ void SignExtend(inout float3 v1, uint mask, uint signFlag) {
v1 = v;
}
+// Encodes a block with mode 11 (2x 10-bit endpoints).
void EncodeP1(inout uint4 block, inout float blockMSLE, float3 texels[16]) {
// compute endpoints (min/max RGB bbox)
float3 blockMin = texels[0];
@@ -250,6 +291,12 @@ void EncodeP1(inout uint4 block, inout float blockMSLE, float3 texels[16]) {
float endPoint0Pos = f32tof16(dot(blockMin, blockDir));
float endPoint1Pos = f32tof16(dot(blockMax, blockDir));
+#ifdef SIGNED
+ int maxVal10 = 0x1FF;
+ endpoint0 = clamp(endpoint0, -maxVal10, maxVal10);
+ endpoint1 = clamp(endpoint1, -maxVal10, maxVal10);
+#endif
+
// check if endpoint swap is required
float fixupTexelPos = f32tof16(dot(texels[0], blockDir));
uint fixupIndex = ComputeIndex4(fixupTexelPos, endPoint0Pos, endPoint1Pos);
@@ -276,6 +323,11 @@ void EncodeP1(inout uint4 block, inout float blockMSLE, float3 texels[16]) {
msle += CalcMSLE(texels[i], texelUnc);
}
+#ifdef SIGNED
+ SignExtend(endpoint0, 0x1FF, 0x200);
+ SignExtend(endpoint1, 0x1FF, 0x200);
+#endif
+
// encode block for mode 11
blockMSLE = msle;
block.x = 0x03;
@@ -316,11 +368,12 @@ float DistToLineSq(float3 PointOnLine, float3 LineDirection, float3 Point) {
return dot(x, x);
}
+// Gets the deviation from the source data of a particular pattern (smaller is better).
float EvaluateP2Pattern(uint pattern, float3 texels[16]) {
float3 p0BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX);
- float3 p0BlockMax = float3(0.0f, 0.0f, 0.0f);
+ float3 p0BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN);
float3 p1BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX);
- float3 p1BlockMax = float3(0.0f, 0.0f, 0.0f);
+ float3 p1BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN);
for (uint i = 0; i < 16; ++i) {
uint paletteID = Pattern(pattern, i);
@@ -350,11 +403,12 @@ float EvaluateP2Pattern(uint pattern, float3 texels[16]) {
return sqDistanceFromLine;
}
+// Encodes a block with either mode 2 (7-bit base, 3x 6-bit delta), or mode 6 (9-bit base, 3x 5-bit delta). Both use pattern encoding.
void EncodeP2Pattern(inout uint4 block, inout float blockMSLE, uint pattern, float3 texels[16]) {
float3 p0BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX);
- float3 p0BlockMax = float3(0.0f, 0.0f, 0.0f);
+ float3 p0BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN);
float3 p1BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX);
- float3 p1BlockMax = float3(0.0f, 0.0f, 0.0f);
+ float3 p1BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN);
for (uint i = 0u; i < 16u; ++i) {
uint paletteID = Pattern(pattern, i);
@@ -430,6 +484,13 @@ void EncodeP2Pattern(inout uint4 block, inout float blockMSLE, uint pattern, flo
endpoint952 = clamp(endpoint952, -maxVal95, maxVal95);
endpoint953 = clamp(endpoint953, -maxVal95, maxVal95);
+#ifdef SIGNED
+ int maxVal7 = 0x3F;
+ int maxVal9 = 0xFF;
+ endpoint760 = clamp(endpoint760, -maxVal7, maxVal7);
+ endpoint950 = clamp(endpoint950, -maxVal9, maxVal9);
+#endif
+
float3 endpoint760Unq = Unquantize7(endpoint760);
float3 endpoint761Unq = Unquantize7(endpoint760 + endpoint761);
float3 endpoint762Unq = Unquantize7(endpoint760 + endpoint762);
@@ -465,6 +526,11 @@ void EncodeP2Pattern(inout uint4 block, inout float blockMSLE, uint pattern, flo
SignExtend(endpoint952, 0xF, 0x10);
SignExtend(endpoint953, 0xF, 0x10);
+#ifdef SIGNED
+ SignExtend(endpoint760, 0x3F, 0x40);
+ SignExtend(endpoint950, 0xFF, 0x100);
+#endif
+
// encode block
float p2MSLE = min(msle76, msle95);
if (p2MSLE < blockMSLE) {
@@ -637,7 +703,7 @@ void main() {
float bestScore = EvaluateP2Pattern(0, texels);
uint bestPattern = 0;
- for (uint i = 1u; i < 32u; ++i) {
+ for (uint i = 1u; i < PATTERN_NUM; ++i) {
float score = EvaluateP2Pattern(i, texels);
if (score < bestScore) {
diff --git a/modules/betsy/image_compress_betsy.cpp b/modules/betsy/image_compress_betsy.cpp
index 6a0862e729..7f723826d1 100644
--- a/modules/betsy/image_compress_betsy.cpp
+++ b/modules/betsy/image_compress_betsy.cpp
@@ -49,31 +49,6 @@ static int get_next_multiple(int n, int m) {
return n + (m - (n % m));
}
-static bool is_image_signed(const Image *r_img) {
- if (r_img->get_format() >= Image::FORMAT_RH && r_img->get_format() <= Image::FORMAT_RGBAH) {
- const uint16_t *img_data = reinterpret_cast<const uint16_t *>(r_img->ptr());
- const uint64_t img_size = r_img->get_data_size() / 2;
-
- for (uint64_t i = 0; i < img_size; i++) {
- if ((img_data[i] & 0x8000) != 0 && (img_data[i] & 0x7fff) != 0) {
- return true;
- }
- }
-
- } else if (r_img->get_format() >= Image::FORMAT_RF && r_img->get_format() <= Image::FORMAT_RGBAF) {
- const uint32_t *img_data = reinterpret_cast<const uint32_t *>(r_img->ptr());
- const uint64_t img_size = r_img->get_data_size() / 4;
-
- for (uint64_t i = 0; i < img_size; i++) {
- if ((img_data[i] & 0x80000000) != 0 && (img_data[i] & 0x7fffffff) != 0) {
- return true;
- }
- }
- }
-
- return false;
-}
-
Error _compress_betsy(BetsyFormat p_format, Image *r_img) {
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
@@ -125,7 +100,7 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) {
case BETSY_FORMAT_BC6: {
err = compute_shader->parse_versions_from_text(bc6h_shader_glsl);
- if (is_image_signed(r_img)) {
+ if (r_img->detect_signed(true)) {
dest_format = Image::FORMAT_BPTC_RGBF;
version = "signed";
} else {
diff --git a/modules/cvtt/image_compress_cvtt.cpp b/modules/cvtt/image_compress_cvtt.cpp
index 6b905c5ea1..2087dde2a1 100644
--- a/modules/cvtt/image_compress_cvtt.cpp
+++ b/modules/cvtt/image_compress_cvtt.cpp
@@ -174,17 +174,7 @@ void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) {
p_image->convert(Image::FORMAT_RGBH);
}
- const uint8_t *rb = p_image->get_data().ptr();
-
- const uint16_t *source_data = reinterpret_cast<const uint16_t *>(&rb[0]);
- int pixel_element_count = w * h * 3;
- for (int i = 0; i < pixel_element_count; i++) {
- if ((source_data[i] & 0x8000) != 0 && (source_data[i] & 0x7fff) != 0) {
- is_signed = true;
- break;
- }
- }
-
+ is_signed = p_image->detect_signed();
target_format = is_signed ? Image::FORMAT_BPTC_RGBF : Image::FORMAT_BPTC_RGBFU;
} else {
p_image->convert(Image::FORMAT_RGBA8); //still uses RGBA to convert
diff --git a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp
index daa4db37df..f5b19f803a 100644
--- a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp
+++ b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp
@@ -124,7 +124,7 @@ Node *EditorSceneFormatImporterFBX2GLTF::import_scene(const String &p_path, uint
#endif
}
-Variant EditorSceneFormatImporterFBX2GLTF::get_option_visibility(const String &p_path, bool p_for_animation,
+Variant EditorSceneFormatImporterFBX2GLTF::get_option_visibility(const String &p_path, const String &p_scene_import_type,
const String &p_option, const HashMap<StringName, Variant> &p_options) {
// Remove all the FBX options except for 'fbx/importer' if the importer is fbx2gltf.
// These options are available only for ufbx.
diff --git a/modules/fbx/editor/editor_scene_importer_fbx2gltf.h b/modules/fbx/editor/editor_scene_importer_fbx2gltf.h
index c68e37f0d8..ce2bac6fcf 100644
--- a/modules/fbx/editor/editor_scene_importer_fbx2gltf.h
+++ b/modules/fbx/editor/editor_scene_importer_fbx2gltf.h
@@ -49,7 +49,7 @@ public:
List<String> *r_missing_deps, Error *r_err = nullptr) override;
virtual void get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) override;
- virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
+ virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option,
const HashMap<StringName, Variant> &p_options) override;
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
};
diff --git a/modules/fbx/editor/editor_scene_importer_ufbx.cpp b/modules/fbx/editor/editor_scene_importer_ufbx.cpp
index 4d5f220539..64075c0664 100644
--- a/modules/fbx/editor/editor_scene_importer_ufbx.cpp
+++ b/modules/fbx/editor/editor_scene_importer_ufbx.cpp
@@ -88,7 +88,7 @@ Node *EditorSceneFormatImporterUFBX::import_scene(const String &p_path, uint32_t
return fbx->generate_scene(state, state->get_bake_fps(), (bool)p_options["animation/trimming"], false);
}
-Variant EditorSceneFormatImporterUFBX::get_option_visibility(const String &p_path, bool p_for_animation,
+Variant EditorSceneFormatImporterUFBX::get_option_visibility(const String &p_path, const String &p_scene_import_type,
const String &p_option, const HashMap<StringName, Variant> &p_options) {
return true;
}
diff --git a/modules/fbx/editor/editor_scene_importer_ufbx.h b/modules/fbx/editor/editor_scene_importer_ufbx.h
index b81b8df4c1..6e3eafc100 100644
--- a/modules/fbx/editor/editor_scene_importer_ufbx.h
+++ b/modules/fbx/editor/editor_scene_importer_ufbx.h
@@ -53,7 +53,7 @@ public:
List<String> *r_missing_deps, Error *r_err = nullptr) override;
virtual void get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) override;
- virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
+ virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option,
const HashMap<StringName, Variant> &p_options) override;
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
};
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 7bf5e946fb..e3f2a61090 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -53,9 +53,11 @@
#include "core/io/file_access_encrypted.h"
#include "core/os/os.h"
+#include "scene/resources/packed_scene.h"
#include "scene/scene_string_names.h"
#ifdef TOOLS_ENABLED
+#include "core/extension/gdextension_manager.h"
#include "editor/editor_paths.h"
#endif
@@ -952,7 +954,8 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const {
if (E) {
if (likely(top->valid) && E->value.getter) {
Callable::CallError ce;
- r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
+ const Variant ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce);
+ r_ret = (ce.error == Callable::CallError::CALL_OK) ? ret : Variant();
return true;
}
r_ret = top->static_variables[E->value.index];
@@ -1725,10 +1728,9 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
if (E) {
if (likely(script->valid) && E->value.getter) {
Callable::CallError err;
- r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err);
- if (err.error == Callable::CallError::CALL_OK) {
- return true;
- }
+ const Variant ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err);
+ r_ret = (err.error == Callable::CallError::CALL_OK) ? ret : Variant();
+ return true;
}
r_ret = members[E->value.index];
return true;
@@ -1750,7 +1752,8 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
if (E) {
if (likely(sptr->valid) && E->value.getter) {
Callable::CallError ce;
- r_ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce);
+ const Variant ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce);
+ r_ret = (ce.error == Callable::CallError::CALL_OK) ? ret : Variant();
return true;
}
r_ret = sptr->static_variables[E->value.index];
@@ -2176,9 +2179,26 @@ void GDScriptLanguage::_add_global(const StringName &p_name, const Variant &p_va
global_array.write[globals[p_name]] = p_value;
return;
}
- globals[p_name] = global_array.size();
- global_array.push_back(p_value);
- _global_array = global_array.ptrw();
+
+ if (global_array_empty_indexes.size()) {
+ int index = global_array_empty_indexes[global_array_empty_indexes.size() - 1];
+ globals[p_name] = index;
+ global_array.write[index] = p_value;
+ global_array_empty_indexes.resize(global_array_empty_indexes.size() - 1);
+ } else {
+ globals[p_name] = global_array.size();
+ global_array.push_back(p_value);
+ _global_array = global_array.ptrw();
+ }
+}
+
+void GDScriptLanguage::_remove_global(const StringName &p_name) {
+ if (!globals.has(p_name)) {
+ return;
+ }
+ global_array_empty_indexes.push_back(globals[p_name]);
+ global_array.write[globals[p_name]] = Variant::NIL;
+ globals.erase(p_name);
}
void GDScriptLanguage::add_global_constant(const StringName &p_variable, const Variant &p_value) {
@@ -2236,11 +2256,40 @@ void GDScriptLanguage::init() {
_add_global(E.name, E.ptr);
}
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ GDExtensionManager::get_singleton()->connect("extension_loaded", callable_mp(this, &GDScriptLanguage::_extension_loaded));
+ GDExtensionManager::get_singleton()->connect("extension_unloading", callable_mp(this, &GDScriptLanguage::_extension_unloading));
+ }
+#endif
+
#ifdef TESTS_ENABLED
GDScriptTests::GDScriptTestRunner::handle_cmdline();
#endif
}
+#ifdef TOOLS_ENABLED
+void GDScriptLanguage::_extension_loaded(const Ref<GDExtension> &p_extension) {
+ List<StringName> class_list;
+ ClassDB::get_extension_class_list(p_extension, &class_list);
+ for (const StringName &n : class_list) {
+ if (globals.has(n)) {
+ continue;
+ }
+ Ref<GDScriptNativeClass> nc = memnew(GDScriptNativeClass(n));
+ _add_global(n, nc);
+ }
+}
+
+void GDScriptLanguage::_extension_unloading(const Ref<GDExtension> &p_extension) {
+ List<StringName> class_list;
+ ClassDB::get_extension_class_list(p_extension, &class_list);
+ for (const StringName &n : class_list) {
+ _remove_global(n);
+ }
+}
+#endif
+
String GDScriptLanguage::get_type() const {
return "GDScript";
}
@@ -2503,7 +2552,7 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
SelfList<GDScript> *elem = script_list.first();
while (elem) {
// Scripts will reload all subclasses, so only reload root scripts.
- if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) {
+ if (elem->self()->is_root_script() && !elem->self()->get_path().is_empty()) {
scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident
}
elem = elem->next();
@@ -2571,7 +2620,19 @@ void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload
for (KeyValue<Ref<GDScript>, HashMap<ObjectID, List<Pair<StringName, Variant>>>> &E : to_reload) {
Ref<GDScript> scr = E.key;
print_verbose("GDScript: Reloading: " + scr->get_path());
- scr->load_source_code(scr->get_path());
+ if (scr->is_built_in()) {
+ // TODO: It would be nice to do it more efficiently than loading the whole scene again.
+ Ref<PackedScene> scene = ResourceLoader::load(scr->get_path().get_slice("::", 0), "", ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP);
+ ERR_CONTINUE(scene.is_null());
+
+ Ref<SceneState> state = scene->get_state();
+ Ref<GDScript> fresh = state->get_sub_resource(scr->get_path());
+ ERR_CONTINUE(fresh.is_null());
+
+ scr->set_source_code(fresh->get_source_code());
+ } else {
+ scr->load_source_code(scr->get_path());
+ }
scr->reload(p_soft_reload);
//restore state if saved
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 6527a0ea4d..4d21651365 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -417,6 +417,7 @@ class GDScriptLanguage : public ScriptLanguage {
Vector<Variant> global_array;
HashMap<StringName, int> globals;
HashMap<StringName, Variant> named_globals;
+ Vector<int> global_array_empty_indexes;
struct CallLevel {
Variant *stack = nullptr;
@@ -448,6 +449,7 @@ class GDScriptLanguage : public ScriptLanguage {
int _debug_max_call_stack = 0;
void _add_global(const StringName &p_name, const Variant &p_value);
+ void _remove_global(const StringName &p_name);
friend class GDScriptInstance;
@@ -467,6 +469,11 @@ class GDScriptLanguage : public ScriptLanguage {
HashMap<String, ObjectID> orphan_subclasses;
+#ifdef TOOLS_ENABLED
+ void _extension_loaded(const Ref<GDExtension> &p_extension);
+ void _extension_unloading(const Ref<GDExtension> &p_extension);
+#endif
+
public:
int calls;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index e98cae765b..7e29a9c0fe 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -418,6 +418,12 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
return err;
}
+#ifdef DEBUG_ENABLED
+ if (!parser->_is_tool && ext_parser->get_parser()->_is_tool) {
+ parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL);
+ }
+#endif
+
base = ext_parser->get_parser()->head->get_datatype();
} else {
if (p_class->extends.is_empty()) {
@@ -445,6 +451,13 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), id);
return err;
}
+
+#ifdef DEBUG_ENABLED
+ if (!parser->_is_tool && base_parser->get_parser()->_is_tool) {
+ parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL);
+ }
+#endif
+
base = base_parser->get_parser()->head->get_datatype();
}
} else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) {
@@ -465,6 +478,13 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), id);
return err;
}
+
+#ifdef DEBUG_ENABLED
+ if (!parser->_is_tool && info_parser->get_parser()->_is_tool) {
+ parser->push_warning(p_class, GDScriptWarning::MISSING_TOOL);
+ }
+#endif
+
base = info_parser->get_parser()->head->get_datatype();
} else if (class_exists(name)) {
if (Engine::get_singleton()->has_singleton(name)) {
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index a6fd3f94da..636339ef1d 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -97,8 +97,8 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri
}
processed_template = processed_template.replace("_BASE_", p_base_class_name)
- .replace("_CLASS_SNAKE_CASE_", p_class_name.to_snake_case().validate_identifier())
- .replace("_CLASS_", p_class_name.to_pascal_case().validate_identifier())
+ .replace("_CLASS_SNAKE_CASE_", p_class_name.to_snake_case().validate_ascii_identifier())
+ .replace("_CLASS_", p_class_name.to_pascal_case().validate_ascii_identifier())
.replace("_TS_", _get_indentation());
scr->set_source_code(processed_template);
return scr;
@@ -3486,7 +3486,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
// is not a valid identifier.
bool path_needs_quote = false;
for (const String &part : opt.split("/")) {
- if (!part.is_valid_identifier()) {
+ 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 759e92d68c..ac4bab6d84 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -509,7 +509,7 @@ private:
} profile;
#endif
- _FORCE_INLINE_ String _get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const;
+ _FORCE_INLINE_ String _get_call_error(const String &p_where, const Variant **p_argptrs, const Variant &p_ret, const Callable::CallError &p_err) const;
Variant _get_default_variant_for_data_type(const GDScriptDataType &p_data_type);
public:
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index ecef852b4b..582305d900 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -260,6 +260,7 @@ void GDScriptParser::override_completion_context(const Node *p_for_node, Complet
context.current_line = tokenizer->get_cursor_line();
context.current_argument = p_argument;
context.node = p_node;
+ context.parser = this;
completion_context = context;
}
@@ -3122,6 +3123,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode *
subscript->base = p_previous_operand;
subscript->index = parse_expression(false);
+#ifdef TOOLS_ENABLED
+ if (subscript->index != nullptr && subscript->index->type == Node::LITERAL) {
+ override_completion_context(subscript->index, COMPLETION_SUBSCRIPT, subscript);
+ }
+#endif
+
if (subscript->index == nullptr) {
push_error(R"(Expected expression after "[".)");
}
@@ -4086,7 +4093,7 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
return true;
}
-bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
#ifdef DEBUG_ENABLED
if (_is_tool) {
push_error(R"("@tool" annotation can only be used once.)", p_annotation);
@@ -4097,7 +4104,7 @@ bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p
return true;
}
-bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)");
ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);
@@ -4128,7 +4135,7 @@ bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p
return true;
}
-bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)");
if (current_class && !ClassDB::is_parent_class(current_class->get_datatype().native_type, SNAME("Node"))) {
@@ -4261,7 +4268,7 @@ static StringName _find_narrowest_native_or_global_class(const GDScriptParser::D
}
template <PropertyHint t_hint, Variant::Type t_type>
-bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
ERR_FAIL_NULL_V(p_class, false);
@@ -4500,7 +4507,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
// For `@export_storage` and `@export_custom`, there is no need to check the variable type, argument values,
// or handle array exports in a special way, so they are implemented as separate methods.
-bool GDScriptParser::export_storage_annotation(const AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
+bool GDScriptParser::export_storage_annotation(AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
VariableNode *variable = static_cast<VariableNode *>(p_node);
@@ -4522,7 +4529,7 @@ bool GDScriptParser::export_storage_annotation(const AnnotationNode *p_annotatio
return true;
}
-bool GDScriptParser::export_custom_annotation(const AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
+bool GDScriptParser::export_custom_annotation(AnnotationNode *p_annotation, Node *p_node, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
ERR_FAIL_COND_V_MSG(p_annotation->resolved_arguments.size() < 2, false, R"(Annotation "@export_custom" requires 2 arguments.)");
@@ -4551,31 +4558,29 @@ bool GDScriptParser::export_custom_annotation(const AnnotationNode *p_annotation
}
template <PropertyUsageFlags t_usage>
-bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
- AnnotationNode *annotation = const_cast<AnnotationNode *>(p_annotation);
-
- if (annotation->resolved_arguments.is_empty()) {
+bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+ if (p_annotation->resolved_arguments.is_empty()) {
return false;
}
- annotation->export_info.name = annotation->resolved_arguments[0];
+ p_annotation->export_info.name = p_annotation->resolved_arguments[0];
switch (t_usage) {
case PROPERTY_USAGE_CATEGORY: {
- annotation->export_info.usage = t_usage;
+ p_annotation->export_info.usage = t_usage;
} break;
case PROPERTY_USAGE_GROUP: {
- annotation->export_info.usage = t_usage;
- if (annotation->resolved_arguments.size() == 2) {
- annotation->export_info.hint_string = annotation->resolved_arguments[1];
+ p_annotation->export_info.usage = t_usage;
+ if (p_annotation->resolved_arguments.size() == 2) {
+ p_annotation->export_info.hint_string = p_annotation->resolved_arguments[1];
}
} break;
case PROPERTY_USAGE_SUBGROUP: {
- annotation->export_info.usage = t_usage;
- if (annotation->resolved_arguments.size() == 2) {
- annotation->export_info.hint_string = annotation->resolved_arguments[1];
+ p_annotation->export_info.usage = t_usage;
+ if (p_annotation->resolved_arguments.size() == 2) {
+ p_annotation->export_info.hint_string = p_annotation->resolved_arguments[1];
}
} break;
}
@@ -4583,7 +4588,7 @@ bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation
return true;
}
-bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
#ifndef DEBUG_ENABLED
// Only available in debug builds.
return true;
@@ -4658,7 +4663,7 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod
#endif // DEBUG_ENABLED
}
-bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to functions.)", p_annotation->name));
FunctionNode *function = static_cast<FunctionNode *>(p_target);
@@ -4719,7 +4724,7 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
return true;
}
-bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+bool GDScriptParser::static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name));
ClassNode *class_node = static_cast<ClassNode *>(p_target);
if (class_node->annotated_static_unload) {
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 2999fb11e4..43c5a48fa7 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -1371,7 +1371,7 @@ private:
bool in_lambda = false;
bool lambda_ended = false; // Marker for when a lambda ends, to apply an end of statement if needed.
- typedef bool (GDScriptParser::*AnnotationAction)(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ typedef bool (GDScriptParser::*AnnotationAction)(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
struct AnnotationInfo {
enum TargetKind {
NONE = 0,
@@ -1495,18 +1495,18 @@ private:
static bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false);
bool validate_annotation_arguments(AnnotationNode *p_annotation);
void clear_unused_annotations();
- bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
- bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
- bool onready_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
template <PropertyHint t_hint, Variant::Type t_type>
- bool export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
- bool export_storage_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
- bool export_custom_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool export_storage_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool export_custom_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
template <PropertyUsageFlags t_usage>
- bool export_group_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
- bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
- bool rpc_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
- bool static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+ bool static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
// Statements.
Node *parse_statement();
VariableNode *parse_variable(bool p_is_static);
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index 5eab8a6306..ddb0cf9502 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -134,38 +134,36 @@ Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataT
return Variant();
}
-String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const {
- String err_text;
-
- if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) {
- int errorarg = p_err.argument;
- ERR_FAIL_COND_V_MSG(errorarg < 0 || argptrs[errorarg] == nullptr, "GDScript bug (please report): Invalid CallError argument index or null pointer.", "Invalid CallError argument index or null pointer.");
- // Handle the Object to Object case separately as we don't have further class details.
+String GDScriptFunction::_get_call_error(const String &p_where, const Variant **p_argptrs, const Variant &p_ret, const Callable::CallError &p_err) const {
+ switch (p_err.error) {
+ case Callable::CallError::CALL_OK:
+ return String();
+ case Callable::CallError::CALL_ERROR_INVALID_METHOD:
+ if (p_ret.get_type() == Variant::STRING && !p_ret.operator String().is_empty()) {
+ return "Invalid call " + p_where + ": " + p_ret.operator String();
+ }
+ return "Invalid call. Nonexistent " + p_where + ".";
+ case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT:
+ ERR_FAIL_COND_V_MSG(p_err.argument < 0 || p_argptrs[p_err.argument] == nullptr, "Bug: Invalid CallError argument index or null pointer.", "Bug: Invalid CallError argument index or null pointer.");
+ // Handle the Object to Object case separately as we don't have further class details.
#ifdef DEBUG_ENABLED
- if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) {
- err_text = "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") is not a subclass of the expected argument class.";
- } else if (p_err.expected == Variant::ARRAY && argptrs[errorarg]->get_type() == p_err.expected) {
- err_text = "Invalid type in " + p_where + ". The array of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") does not have the same element type as the expected typed array argument.";
- } else
+ if (p_err.expected == Variant::OBJECT && p_argptrs[p_err.argument]->get_type() == p_err.expected) {
+ return "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") is not a subclass of the expected argument class.";
+ }
+ 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.";
+ }
#endif // DEBUG_ENABLED
- {
- err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + ".";
- }
- } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) {
- err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments.";
- } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) {
- err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments.";
- } else if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) {
- err_text = "Invalid call. Nonexistent " + p_where + ".";
- } else if (p_err.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) {
- err_text = "Attempt to call " + p_where + " on a null instance.";
- } else if (p_err.error == Callable::CallError::CALL_ERROR_METHOD_NOT_CONST) {
- err_text = "Attempt to call " + p_where + " on a const instance.";
- } else {
- err_text = "Bug, call error: #" + itos(p_err.error);
+ 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:
+ case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS:
+ return "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments.";
+ case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL:
+ return "Attempt to call " + p_where + " on a null instance.";
+ case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST:
+ return "Attempt to call " + p_where + " on a const instance.";
}
-
- return err_text;
+ return "Bug: Invalid call error code " + itos(p_err.error) + ".";
}
void (*type_init_function_table[])(Variant *) = {
@@ -1608,7 +1606,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#ifdef DEBUG_ENABLED
if (err.error != Callable::CallError::CALL_OK) {
- err_text = _get_call_error(err, "'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs);
+ err_text = _get_call_error("'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs, *dst, err);
OPCODE_BREAK;
}
#endif
@@ -1744,10 +1742,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
StringName base_class = base_obj ? base_obj->get_class_name() : StringName();
#endif
+ Variant temp_ret;
Callable::CallError err;
if (call_ret) {
GET_INSTRUCTION_ARG(ret, argc + 1);
- base->callp(*methodname, (const Variant **)argptrs, argc, *ret, err);
+ base->callp(*methodname, (const Variant **)argptrs, argc, temp_ret, err);
+ *ret = temp_ret;
#ifdef DEBUG_ENABLED
if (ret->get_type() == Variant::NIL) {
if (base_type == Variant::OBJECT) {
@@ -1776,8 +1776,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
#endif
} else {
- Variant ret;
- base->callp(*methodname, (const Variant **)argptrs, argc, ret, err);
+ base->callp(*methodname, (const Variant **)argptrs, argc, temp_ret, err);
}
#ifdef DEBUG_ENABLED
@@ -1822,7 +1821,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
}
}
- err_text = _get_call_error(err, "function '" + methodstr + (is_callable ? "" : "' in base '" + basestr) + "'", (const Variant **)argptrs);
+ err_text = _get_call_error("function '" + methodstr + (is_callable ? "" : "' in base '" + basestr) + "'", (const Variant **)argptrs, temp_ret, err);
OPCODE_BREAK;
}
#endif
@@ -1868,12 +1867,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
#endif
+ Variant temp_ret;
Callable::CallError err;
if (call_ret) {
GET_INSTRUCTION_ARG(ret, argc + 1);
- *ret = method->call(base_obj, (const Variant **)argptrs, argc, err);
+ temp_ret = method->call(base_obj, (const Variant **)argptrs, argc, err);
+ *ret = temp_ret;
} else {
- method->call(base_obj, (const Variant **)argptrs, argc, err);
+ temp_ret = method->call(base_obj, (const Variant **)argptrs, argc, err);
}
#ifdef DEBUG_ENABLED
@@ -1906,7 +1907,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
}
}
- err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs);
+ err_text = _get_call_error("function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs, temp_ret, err);
OPCODE_BREAK;
}
#endif
@@ -1939,7 +1940,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#ifdef DEBUG_ENABLED
if (err.error != Callable::CallError::CALL_OK) {
- err_text = _get_call_error(err, "static function '" + methodname->operator String() + "' in type '" + Variant::get_type_name(builtin_type) + "'", argptrs);
+ err_text = _get_call_error("static function '" + methodname->operator String() + "' in type '" + Variant::get_type_name(builtin_type) + "'", argptrs, *ret, err);
OPCODE_BREAK;
}
#endif
@@ -1983,7 +1984,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#endif
if (err.error != Callable::CallError::CALL_OK) {
- err_text = _get_call_error(err, "static function '" + method->get_name().operator String() + "' in type '" + method->get_instance_class().operator String() + "'", argptrs);
+ err_text = _get_call_error("static function '" + method->get_name().operator String() + "' in type '" + method->get_instance_class().operator String() + "'", argptrs, *ret, err);
OPCODE_BREAK;
}
@@ -2214,7 +2215,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
// Call provided error string.
err_text = vformat(R"*(Error calling utility function "%s()": %s)*", methodstr, *dst);
} else {
- err_text = _get_call_error(err, vformat(R"*(utility function "%s()")*", methodstr), (const Variant **)argptrs);
+ err_text = _get_call_error(vformat(R"*(utility function "%s()")*", methodstr), (const Variant **)argptrs, *dst, err);
}
OPCODE_BREAK;
}
@@ -2271,7 +2272,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
// Call provided error string.
err_text = vformat(R"*(Error calling GDScript utility function "%s()": %s)*", methodstr, *dst);
} else {
- err_text = _get_call_error(err, vformat(R"*(GDScript utility function "%s()")*", methodstr), (const Variant **)argptrs);
+ err_text = _get_call_error(vformat(R"*(GDScript utility function "%s()")*", methodstr), (const Variant **)argptrs, *dst, err);
}
OPCODE_BREAK;
}
@@ -2338,7 +2339,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (err.error != Callable::CallError::CALL_OK) {
String methodstr = *methodname;
- err_text = _get_call_error(err, "function '" + methodstr + "'", (const Variant **)argptrs);
+ err_text = _get_call_error("function '" + methodstr + "'", (const Variant **)argptrs, *dst, err);
OPCODE_BREAK;
}
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index e8fb1d94b3..4ffb4bd9d1 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -109,6 +109,8 @@ String GDScriptWarning::get_message() const {
case STATIC_CALLED_ON_INSTANCE:
CHECK_SYMBOLS(2);
return vformat(R"*(The function "%s()" is a static function but was called from an instance. Instead, it should be directly called from the type: "%s.%s()".)*", symbols[0], symbols[1], symbols[0]);
+ case MISSING_TOOL:
+ return R"(The base class script has the "@tool" annotation, but this script does not have it.)";
case REDUNDANT_STATIC_UNLOAD:
return R"(The "@static_unload" annotation is redundant because the file does not have a class with static variables.)";
case REDUNDANT_AWAIT:
@@ -219,6 +221,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"UNSAFE_VOID_RETURN",
"RETURN_VALUE_DISCARDED",
"STATIC_CALLED_ON_INSTANCE",
+ "MISSING_TOOL",
"REDUNDANT_STATIC_UNLOAD",
"REDUNDANT_AWAIT",
"ASSERT_ALWAYS_TRUE",
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index 1c806bb4e2..ffcf00a830 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -70,6 +70,7 @@ public:
UNSAFE_VOID_RETURN, // Function returns void but returned a call to a function that can't be type checked.
RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used.
STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself.
+ MISSING_TOOL, // The base class script has the "@tool" annotation, but this script does not have it.
REDUNDANT_STATIC_UNLOAD, // The `@static_unload` annotation is used but the class does not have static data.
REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine).
ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true.
@@ -123,6 +124,7 @@ public:
WARN, // UNSAFE_VOID_RETURN
IGNORE, // RETURN_VALUE_DISCARDED // Too spammy by default on common cases (connect, Tween, etc.).
WARN, // STATIC_CALLED_ON_INSTANCE
+ WARN, // MISSING_TOOL
WARN, // REDUNDANT_STATIC_UNLOAD
WARN, // REDUNDANT_AWAIT
WARN, // ASSERT_ALWAYS_TRUE
diff --git a/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd
index efd8ad6edb..60bcde4b8c 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd
@@ -3,14 +3,13 @@ const const_color: Color = 'red'
func func_color(arg_color: Color = 'blue') -> bool:
return arg_color == Color.BLUE
-@warning_ignore("assert_always_true")
func test():
- assert(const_color == Color.RED)
+ Utils.check(const_color == Color.RED)
- assert(func_color() == true)
- assert(func_color('blue') == true)
+ Utils.check(func_color() == true)
+ Utils.check(func_color('blue') == true)
var var_color: Color = 'green'
- assert(var_color == Color.GREEN)
+ Utils.check(var_color == Color.GREEN)
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
index bed9dd0e96..5318d11f33 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
@@ -5,20 +5,19 @@ const const_float_cast: float = 76 as float
const const_packed_empty: PackedFloat64Array = []
const const_packed_ints: PackedFloat64Array = [52]
-@warning_ignore("assert_always_true")
func test():
- assert(typeof(const_float_int) == TYPE_FLOAT)
- assert(str(const_float_int) == '19')
- assert(typeof(const_float_plus) == TYPE_FLOAT)
- assert(str(const_float_plus) == '34')
- assert(typeof(const_float_cast) == TYPE_FLOAT)
- assert(str(const_float_cast) == '76')
+ Utils.check(typeof(const_float_int) == TYPE_FLOAT)
+ Utils.check(str(const_float_int) == '19')
+ Utils.check(typeof(const_float_plus) == TYPE_FLOAT)
+ Utils.check(str(const_float_plus) == '34')
+ Utils.check(typeof(const_float_cast) == TYPE_FLOAT)
+ Utils.check(str(const_float_cast) == '76')
- assert(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY)
- assert(str(const_packed_empty) == '[]')
- assert(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY)
- assert(str(const_packed_ints) == '[52]')
- assert(typeof(const_packed_ints[0]) == TYPE_FLOAT)
- assert(str(const_packed_ints[0]) == '52')
+ Utils.check(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY)
+ Utils.check(str(const_packed_empty) == '[]')
+ Utils.check(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY)
+ Utils.check(str(const_packed_ints) == '[52]')
+ Utils.check(typeof(const_packed_ints[0]) == TYPE_FLOAT)
+ Utils.check(str(const_packed_ints[0]) == '52')
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd b/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd
index d2d9d04508..a569488d3c 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd
@@ -5,5 +5,5 @@ func test():
for value in range(E.E0, E.E3):
var inferable := value
total += inferable
- assert(total == 0 + 1 + 2)
+ Utils.check(total == 0 + 1 + 2)
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
index 39f490c4b3..ec89226328 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
@@ -2,8 +2,6 @@ class_name TestExportEnumAsDictionary
enum MyEnum {A, B, C}
-const Utils = preload("../../utils.notest.gd")
-
@export var test_1 = MyEnum
@export var test_2 = MyEnum.A
@export var test_3 := MyEnum
diff --git a/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd
index 4a7f10f1ee..9ce0782d5c 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd
@@ -3,5 +3,5 @@ func test():
var result := ''
for i in range(array.size(), 0, -1):
result += str(array[i - 1])
- assert(result == '963')
+ Utils.check(result == '963')
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd b/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd
index d678f3acfc..e0cbdacb38 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd
@@ -2,11 +2,11 @@ func test():
var instance := Parent.new()
var result := instance.my_function(1)
print(result)
- assert(result == 1)
+ Utils.check(result == 1)
instance = Child.new()
result = instance.my_function(2)
print(result)
- assert(result == 0)
+ Utils.check(result == 0)
class Parent:
func my_function(par1: int) -> int:
diff --git a/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd
index 0b1576e66e..cbe8e9da34 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd
@@ -8,27 +8,27 @@ func convert_var_array_to_packed() -> PackedStringArray: var array := ['79']; re
func test():
var converted_literal_int := convert_literal_int_to_float()
- assert(typeof(converted_literal_int) == TYPE_FLOAT)
- assert(converted_literal_int == 76.0)
+ Utils.check(typeof(converted_literal_int) == TYPE_FLOAT)
+ Utils.check(converted_literal_int == 76.0)
var converted_arg_int := convert_arg_int_to_float(36)
- assert(typeof(converted_arg_int) == TYPE_FLOAT)
- assert(converted_arg_int == 36.0)
+ Utils.check(typeof(converted_arg_int) == TYPE_FLOAT)
+ Utils.check(converted_arg_int == 36.0)
var converted_var_int := convert_var_int_to_float()
- assert(typeof(converted_var_int) == TYPE_FLOAT)
- assert(converted_var_int == 59.0)
+ Utils.check(typeof(converted_var_int) == TYPE_FLOAT)
+ Utils.check(converted_var_int == 59.0)
var converted_literal_array := convert_literal_array_to_packed()
- assert(typeof(converted_literal_array) == TYPE_PACKED_STRING_ARRAY)
- assert(str(converted_literal_array) == '["46"]')
+ Utils.check(typeof(converted_literal_array) == TYPE_PACKED_STRING_ARRAY)
+ Utils.check(str(converted_literal_array) == '["46"]')
var converted_arg_array := convert_arg_array_to_packed(['91'])
- assert(typeof(converted_arg_array) == TYPE_PACKED_STRING_ARRAY)
- assert(str(converted_arg_array) == '["91"]')
+ Utils.check(typeof(converted_arg_array) == TYPE_PACKED_STRING_ARRAY)
+ Utils.check(str(converted_arg_array) == '["91"]')
var converted_var_array := convert_var_array_to_packed()
- assert(typeof(converted_var_array) == TYPE_PACKED_STRING_ARRAY)
- assert(str(converted_var_array) == '["79"]')
+ Utils.check(typeof(converted_var_array) == TYPE_PACKED_STRING_ARRAY)
+ Utils.check(str(converted_var_array) == '["79"]')
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd
index 44ca5f4dd0..d49acaacd3 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd
@@ -2,7 +2,7 @@ func test():
var left_hard_int := 1
var right_hard_int := 2
var result_hard_int := left_hard_int if true else right_hard_int
- assert(result_hard_int == 1)
+ Utils.check(result_hard_int == 1)
@warning_ignore("inference_on_variant")
var left_hard_variant := 1 as Variant
@@ -10,6 +10,6 @@ func test():
var right_hard_variant := 2.0 as Variant
@warning_ignore("inference_on_variant")
var result_hard_variant := left_hard_variant if true else right_hard_variant
- assert(result_hard_variant == 1)
+ Utils.check(result_hard_variant == 1)
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd
index 12dc0b93df..ee30f01dfb 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd
@@ -4,124 +4,123 @@ class A extends RefCounted:
class B extends A:
pass
-@warning_ignore("assert_always_true")
func test():
var builtin: Variant = 3
- assert((builtin is Variant) == true)
- assert((builtin is int) == true)
- assert(is_instance_of(builtin, TYPE_INT) == true)
- assert((builtin is float) == false)
- assert(is_instance_of(builtin, TYPE_FLOAT) == false)
+ Utils.check((builtin is Variant) == true)
+ Utils.check((builtin is int) == true)
+ Utils.check(is_instance_of(builtin, TYPE_INT) == true)
+ Utils.check((builtin is float) == false)
+ Utils.check(is_instance_of(builtin, TYPE_FLOAT) == false)
const const_builtin: Variant = 3
- assert((const_builtin is Variant) == true)
- assert((const_builtin is int) == true)
- assert(is_instance_of(const_builtin, TYPE_INT) == true)
- assert((const_builtin is float) == false)
- assert(is_instance_of(const_builtin, TYPE_FLOAT) == false)
+ Utils.check((const_builtin is Variant) == true)
+ Utils.check((const_builtin is int) == true)
+ Utils.check(is_instance_of(const_builtin, TYPE_INT) == true)
+ Utils.check((const_builtin is float) == false)
+ Utils.check(is_instance_of(const_builtin, TYPE_FLOAT) == false)
var int_array: Variant = [] as Array[int]
- assert((int_array is Variant) == true)
- assert((int_array is Array) == true)
- assert(is_instance_of(int_array, TYPE_ARRAY) == true)
- assert((int_array is Array[int]) == true)
- assert((int_array is Array[float]) == false)
- assert((int_array is int) == false)
- assert(is_instance_of(int_array, TYPE_INT) == false)
+ Utils.check((int_array is Variant) == true)
+ Utils.check((int_array is Array) == true)
+ Utils.check(is_instance_of(int_array, TYPE_ARRAY) == true)
+ Utils.check((int_array is Array[int]) == true)
+ Utils.check((int_array is Array[float]) == false)
+ Utils.check((int_array is int) == false)
+ Utils.check(is_instance_of(int_array, TYPE_INT) == false)
var const_int_array: Variant = [] as Array[int]
- assert((const_int_array is Variant) == true)
- assert((const_int_array is Array) == true)
- assert(is_instance_of(const_int_array, TYPE_ARRAY) == true)
- assert((const_int_array is Array[int]) == true)
- assert((const_int_array is Array[float]) == false)
- assert((const_int_array is int) == false)
- assert(is_instance_of(const_int_array, TYPE_INT) == false)
+ Utils.check((const_int_array is Variant) == true)
+ Utils.check((const_int_array is Array) == true)
+ Utils.check(is_instance_of(const_int_array, TYPE_ARRAY) == true)
+ Utils.check((const_int_array is Array[int]) == true)
+ Utils.check((const_int_array is Array[float]) == false)
+ Utils.check((const_int_array is int) == false)
+ Utils.check(is_instance_of(const_int_array, TYPE_INT) == false)
var b_array: Variant = [] as Array[B]
- assert((b_array is Variant) == true)
- assert((b_array is Array) == true)
- assert(is_instance_of(b_array, TYPE_ARRAY) == true)
- assert((b_array is Array[B]) == true)
- assert((b_array is Array[A]) == false)
- assert((b_array is Array[int]) == false)
- assert((b_array is int) == false)
- assert(is_instance_of(b_array, TYPE_INT) == false)
+ Utils.check((b_array is Variant) == true)
+ Utils.check((b_array is Array) == true)
+ Utils.check(is_instance_of(b_array, TYPE_ARRAY) == true)
+ Utils.check((b_array is Array[B]) == true)
+ Utils.check((b_array is Array[A]) == false)
+ Utils.check((b_array is Array[int]) == false)
+ Utils.check((b_array is int) == false)
+ Utils.check(is_instance_of(b_array, TYPE_INT) == false)
var const_b_array: Variant = [] as Array[B]
- assert((const_b_array is Variant) == true)
- assert((const_b_array is Array) == true)
- assert(is_instance_of(const_b_array, TYPE_ARRAY) == true)
- assert((const_b_array is Array[B]) == true)
- assert((const_b_array is Array[A]) == false)
- assert((const_b_array is Array[int]) == false)
- assert((const_b_array is int) == false)
- assert(is_instance_of(const_b_array, TYPE_INT) == false)
+ Utils.check((const_b_array is Variant) == true)
+ Utils.check((const_b_array is Array) == true)
+ Utils.check(is_instance_of(const_b_array, TYPE_ARRAY) == true)
+ Utils.check((const_b_array is Array[B]) == true)
+ Utils.check((const_b_array is Array[A]) == false)
+ Utils.check((const_b_array is Array[int]) == false)
+ Utils.check((const_b_array is int) == false)
+ Utils.check(is_instance_of(const_b_array, TYPE_INT) == false)
var native: Variant = RefCounted.new()
- assert((native is Variant) == true)
- assert((native is Object) == true)
- assert(is_instance_of(native, TYPE_OBJECT) == true)
- assert(is_instance_of(native, Object) == true)
- assert((native is RefCounted) == true)
- assert(is_instance_of(native, RefCounted) == true)
- assert((native is Node) == false)
- assert(is_instance_of(native, Node) == false)
- assert((native is int) == false)
- assert(is_instance_of(native, TYPE_INT) == false)
+ Utils.check((native is Variant) == true)
+ Utils.check((native is Object) == true)
+ Utils.check(is_instance_of(native, TYPE_OBJECT) == true)
+ Utils.check(is_instance_of(native, Object) == true)
+ Utils.check((native is RefCounted) == true)
+ Utils.check(is_instance_of(native, RefCounted) == true)
+ Utils.check((native is Node) == false)
+ Utils.check(is_instance_of(native, Node) == false)
+ Utils.check((native is int) == false)
+ Utils.check(is_instance_of(native, TYPE_INT) == false)
var a_script: Variant = A.new()
- assert((a_script is Variant) == true)
- assert((a_script is Object) == true)
- assert(is_instance_of(a_script, TYPE_OBJECT) == true)
- assert(is_instance_of(a_script, Object) == true)
- assert((a_script is RefCounted) == true)
- assert(is_instance_of(a_script, RefCounted) == true)
- assert((a_script is A) == true)
- assert(is_instance_of(a_script, A) == true)
- assert((a_script is B) == false)
- assert(is_instance_of(a_script, B) == false)
- assert((a_script is Node) == false)
- assert(is_instance_of(a_script, Node) == false)
- assert((a_script is int) == false)
- assert(is_instance_of(a_script, TYPE_INT) == false)
+ Utils.check((a_script is Variant) == true)
+ Utils.check((a_script is Object) == true)
+ Utils.check(is_instance_of(a_script, TYPE_OBJECT) == true)
+ Utils.check(is_instance_of(a_script, Object) == true)
+ Utils.check((a_script is RefCounted) == true)
+ Utils.check(is_instance_of(a_script, RefCounted) == true)
+ Utils.check((a_script is A) == true)
+ Utils.check(is_instance_of(a_script, A) == true)
+ Utils.check((a_script is B) == false)
+ Utils.check(is_instance_of(a_script, B) == false)
+ Utils.check((a_script is Node) == false)
+ Utils.check(is_instance_of(a_script, Node) == false)
+ Utils.check((a_script is int) == false)
+ Utils.check(is_instance_of(a_script, TYPE_INT) == false)
var b_script: Variant = B.new()
- assert((b_script is Variant) == true)
- assert((b_script is Object) == true)
- assert(is_instance_of(b_script, TYPE_OBJECT) == true)
- assert(is_instance_of(b_script, Object) == true)
- assert((b_script is RefCounted) == true)
- assert(is_instance_of(b_script, RefCounted) == true)
- assert((b_script is A) == true)
- assert(is_instance_of(b_script, A) == true)
- assert((b_script is B) == true)
- assert(is_instance_of(b_script, B) == true)
- assert((b_script is Node) == false)
- assert(is_instance_of(b_script, Node) == false)
- assert((b_script is int) == false)
- assert(is_instance_of(b_script, TYPE_INT) == false)
+ Utils.check((b_script is Variant) == true)
+ Utils.check((b_script is Object) == true)
+ Utils.check(is_instance_of(b_script, TYPE_OBJECT) == true)
+ Utils.check(is_instance_of(b_script, Object) == true)
+ Utils.check((b_script is RefCounted) == true)
+ Utils.check(is_instance_of(b_script, RefCounted) == true)
+ Utils.check((b_script is A) == true)
+ Utils.check(is_instance_of(b_script, A) == true)
+ Utils.check((b_script is B) == true)
+ Utils.check(is_instance_of(b_script, B) == true)
+ Utils.check((b_script is Node) == false)
+ Utils.check(is_instance_of(b_script, Node) == false)
+ Utils.check((b_script is int) == false)
+ Utils.check(is_instance_of(b_script, TYPE_INT) == false)
var var_null: Variant = null
- assert((var_null is Variant) == true)
- assert((var_null is int) == false)
- assert(is_instance_of(var_null, TYPE_INT) == false)
- assert((var_null is Object) == false)
- assert(is_instance_of(var_null, TYPE_OBJECT) == false)
- assert((var_null is RefCounted) == false)
- assert(is_instance_of(var_null, RefCounted) == false)
- assert((var_null is A) == false)
- assert(is_instance_of(var_null, A) == false)
+ Utils.check((var_null is Variant) == true)
+ Utils.check((var_null is int) == false)
+ Utils.check(is_instance_of(var_null, TYPE_INT) == false)
+ Utils.check((var_null is Object) == false)
+ Utils.check(is_instance_of(var_null, TYPE_OBJECT) == false)
+ Utils.check((var_null is RefCounted) == false)
+ Utils.check(is_instance_of(var_null, RefCounted) == false)
+ Utils.check((var_null is A) == false)
+ Utils.check(is_instance_of(var_null, A) == false)
const const_null: Variant = null
- assert((const_null is Variant) == true)
- assert((const_null is int) == false)
- assert(is_instance_of(const_null, TYPE_INT) == false)
- assert((const_null is Object) == false)
- assert(is_instance_of(const_null, TYPE_OBJECT) == false)
- assert((const_null is RefCounted) == false)
- assert(is_instance_of(const_null, RefCounted) == false)
- assert((const_null is A) == false)
- assert(is_instance_of(const_null, A) == false)
+ Utils.check((const_null is Variant) == true)
+ Utils.check((const_null is int) == false)
+ Utils.check(is_instance_of(const_null, TYPE_INT) == false)
+ Utils.check((const_null is Object) == false)
+ Utils.check(is_instance_of(const_null, TYPE_OBJECT) == false)
+ Utils.check((const_null is RefCounted) == false)
+ Utils.check(is_instance_of(const_null, RefCounted) == false)
+ Utils.check((const_null is A) == false)
+ Utils.check(is_instance_of(const_null, A) == false)
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
index b000c82717..fe0274c27b 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
@@ -10,206 +10,205 @@ class Members:
var two: Array[int] = one
func check_passing() -> bool:
- assert(str(one) == '[104]')
- assert(str(two) == '[104]')
+ Utils.check(str(one) == '[104]')
+ Utils.check(str(two) == '[104]')
two.push_back(582)
- assert(str(one) == '[104, 582]')
- assert(str(two) == '[104, 582]')
+ Utils.check(str(one) == '[104, 582]')
+ Utils.check(str(two) == '[104, 582]')
two = [486]
- assert(str(one) == '[104, 582]')
- assert(str(two) == '[486]')
+ Utils.check(str(one) == '[104, 582]')
+ Utils.check(str(two) == '[486]')
return true
@warning_ignore("unsafe_method_access")
-@warning_ignore("assert_always_true")
@warning_ignore("return_value_discarded")
func test():
var untyped_basic = [459]
- assert(str(untyped_basic) == '[459]')
- assert(untyped_basic.get_typed_builtin() == TYPE_NIL)
+ Utils.check(str(untyped_basic) == '[459]')
+ Utils.check(untyped_basic.get_typed_builtin() == TYPE_NIL)
var inferred_basic := [366]
- assert(str(inferred_basic) == '[366]')
- assert(inferred_basic.get_typed_builtin() == TYPE_NIL)
+ Utils.check(str(inferred_basic) == '[366]')
+ Utils.check(inferred_basic.get_typed_builtin() == TYPE_NIL)
var typed_basic: Array = [521]
- assert(str(typed_basic) == '[521]')
- assert(typed_basic.get_typed_builtin() == TYPE_NIL)
+ Utils.check(str(typed_basic) == '[521]')
+ Utils.check(typed_basic.get_typed_builtin() == TYPE_NIL)
var empty_floats: Array[float] = []
- assert(str(empty_floats) == '[]')
- assert(empty_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(empty_floats) == '[]')
+ Utils.check(empty_floats.get_typed_builtin() == TYPE_FLOAT)
untyped_basic = empty_floats
- assert(untyped_basic.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(untyped_basic.get_typed_builtin() == TYPE_FLOAT)
inferred_basic = empty_floats
- assert(inferred_basic.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(inferred_basic.get_typed_builtin() == TYPE_FLOAT)
typed_basic = empty_floats
- assert(typed_basic.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(typed_basic.get_typed_builtin() == TYPE_FLOAT)
empty_floats.push_back(705.0)
untyped_basic.push_back(430.0)
inferred_basic.push_back(263.0)
typed_basic.push_back(518.0)
- assert(str(empty_floats) == '[705, 430, 263, 518]')
- assert(str(untyped_basic) == '[705, 430, 263, 518]')
- assert(str(inferred_basic) == '[705, 430, 263, 518]')
- assert(str(typed_basic) == '[705, 430, 263, 518]')
+ Utils.check(str(empty_floats) == '[705, 430, 263, 518]')
+ Utils.check(str(untyped_basic) == '[705, 430, 263, 518]')
+ Utils.check(str(inferred_basic) == '[705, 430, 263, 518]')
+ Utils.check(str(typed_basic) == '[705, 430, 263, 518]')
const constant_float := 950.0
const constant_int := 170
var typed_float := 954.0
var filled_floats: Array[float] = [constant_float, constant_int, typed_float, empty_floats[1] + empty_floats[2]]
- assert(str(filled_floats) == '[950, 170, 954, 693]')
- assert(filled_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(filled_floats) == '[950, 170, 954, 693]')
+ Utils.check(filled_floats.get_typed_builtin() == TYPE_FLOAT)
var casted_floats := [empty_floats[2] * 2] as Array[float]
- assert(str(casted_floats) == '[526]')
- assert(casted_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(casted_floats) == '[526]')
+ Utils.check(casted_floats.get_typed_builtin() == TYPE_FLOAT)
var returned_floats = (func () -> Array[float]: return [554]).call()
- assert(str(returned_floats) == '[554]')
- assert(returned_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(returned_floats) == '[554]')
+ Utils.check(returned_floats.get_typed_builtin() == TYPE_FLOAT)
var passed_floats = floats_identity([663.0 if randf() > 0.5 else 663.0])
- assert(str(passed_floats) == '[663]')
- assert(passed_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(passed_floats) == '[663]')
+ Utils.check(passed_floats.get_typed_builtin() == TYPE_FLOAT)
var default_floats = (func (floats: Array[float] = [364.0]): return floats).call()
- assert(str(default_floats) == '[364]')
- assert(default_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(default_floats) == '[364]')
+ Utils.check(default_floats.get_typed_builtin() == TYPE_FLOAT)
var typed_int := 556
var converted_floats: Array[float] = [typed_int]
converted_floats.push_back(498)
- assert(str(converted_floats) == '[556, 498]')
- assert(converted_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(converted_floats) == '[556, 498]')
+ Utils.check(converted_floats.get_typed_builtin() == TYPE_FLOAT)
const constant_basic = [228]
- assert(str(constant_basic) == '[228]')
- assert(constant_basic.get_typed_builtin() == TYPE_NIL)
+ Utils.check(str(constant_basic) == '[228]')
+ Utils.check(constant_basic.get_typed_builtin() == TYPE_NIL)
const constant_floats: Array[float] = [constant_float - constant_basic[0] - constant_int]
- assert(str(constant_floats) == '[552]')
- assert(constant_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(constant_floats) == '[552]')
+ Utils.check(constant_floats.get_typed_builtin() == TYPE_FLOAT)
var source_floats: Array[float] = [999.74]
untyped_basic = source_floats
var destination_floats: Array[float] = untyped_basic
destination_floats[0] -= 0.74
- assert(str(source_floats) == '[999]')
- assert(str(untyped_basic) == '[999]')
- assert(str(destination_floats) == '[999]')
- assert(destination_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(source_floats) == '[999]')
+ Utils.check(str(untyped_basic) == '[999]')
+ Utils.check(str(destination_floats) == '[999]')
+ Utils.check(destination_floats.get_typed_builtin() == TYPE_FLOAT)
var duplicated_floats := empty_floats.duplicate().slice(2, 3)
duplicated_floats[0] *= 3
- assert(str(duplicated_floats) == '[789]')
- assert(duplicated_floats.get_typed_builtin() == TYPE_FLOAT)
+ Utils.check(str(duplicated_floats) == '[789]')
+ Utils.check(duplicated_floats.get_typed_builtin() == TYPE_FLOAT)
var b_objects: Array[B] = [B.new(), B.new() as A, null]
- assert(b_objects.size() == 3)
- assert(b_objects.get_typed_builtin() == TYPE_OBJECT)
- assert(b_objects.get_typed_script() == B)
+ Utils.check(b_objects.size() == 3)
+ Utils.check(b_objects.get_typed_builtin() == TYPE_OBJECT)
+ Utils.check(b_objects.get_typed_script() == B)
var a_objects: Array[A] = [A.new(), B.new(), null, b_objects[0]]
- assert(a_objects.size() == 4)
- assert(a_objects.get_typed_builtin() == TYPE_OBJECT)
- assert(a_objects.get_typed_script() == A)
+ Utils.check(a_objects.size() == 4)
+ Utils.check(a_objects.get_typed_builtin() == TYPE_OBJECT)
+ Utils.check(a_objects.get_typed_script() == A)
var a_passed = (func check_a_passing(p_objects: Array[A]): return p_objects.size()).call(a_objects)
- assert(a_passed == 4)
+ Utils.check(a_passed == 4)
var b_passed = (func check_b_passing(basic: Array): return basic[0] != null).call(b_objects)
- assert(b_passed == true)
+ Utils.check(b_passed == true)
var empty_strings: Array[String] = []
var empty_bools: Array[bool] = []
var empty_basic_one := []
var empty_basic_two := []
- assert(empty_strings == empty_bools)
- assert(empty_basic_one == empty_basic_two)
- assert(empty_strings.hash() == empty_bools.hash())
- assert(empty_basic_one.hash() == empty_basic_two.hash())
+ 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: Array[int] = [527]
var assign_target: Array[int] = []
assign_target.assign(assign_source)
- assert(str(assign_source) == '[527]')
- assert(str(assign_target) == '[527]')
+ Utils.check(str(assign_source) == '[527]')
+ Utils.check(str(assign_target) == '[527]')
assign_source.push_back(657)
- assert(str(assign_source) == '[527, 657]')
- assert(str(assign_target) == '[527]')
+ Utils.check(str(assign_source) == '[527, 657]')
+ Utils.check(str(assign_target) == '[527]')
var defaults_passed = (func check_defaults_passing(one: Array[int] = [], two := one):
one.push_back(887)
two.push_back(198)
- assert(str(one) == '[887, 198]')
- assert(str(two) == '[887, 198]')
+ Utils.check(str(one) == '[887, 198]')
+ Utils.check(str(two) == '[887, 198]')
two = [130]
- assert(str(one) == '[887, 198]')
- assert(str(two) == '[130]')
+ Utils.check(str(one) == '[887, 198]')
+ Utils.check(str(two) == '[130]')
return true
).call()
- assert(defaults_passed == true)
+ Utils.check(defaults_passed == true)
var members := Members.new()
var members_passed := members.check_passing()
- assert(members_passed == true)
+ Utils.check(members_passed == true)
var resized_basic: Array = []
resized_basic.resize(1)
- assert(typeof(resized_basic[0]) == TYPE_NIL)
- assert(resized_basic[0] == null)
+ Utils.check(typeof(resized_basic[0]) == TYPE_NIL)
+ Utils.check(resized_basic[0] == null)
var resized_ints: Array[int] = []
resized_ints.resize(1)
- assert(typeof(resized_ints[0]) == TYPE_INT)
- assert(resized_ints[0] == 0)
+ Utils.check(typeof(resized_ints[0]) == TYPE_INT)
+ Utils.check(resized_ints[0] == 0)
var resized_arrays: Array[Array] = []
resized_arrays.resize(1)
- assert(typeof(resized_arrays[0]) == TYPE_ARRAY)
+ Utils.check(typeof(resized_arrays[0]) == TYPE_ARRAY)
resized_arrays[0].resize(1)
resized_arrays[0][0] = 523
- assert(str(resized_arrays) == '[[523]]')
+ Utils.check(str(resized_arrays) == '[[523]]')
var resized_objects: Array[Object] = []
resized_objects.resize(1)
- assert(typeof(resized_objects[0]) == TYPE_NIL)
- assert(resized_objects[0] == null)
+ Utils.check(typeof(resized_objects[0]) == TYPE_NIL)
+ Utils.check(resized_objects[0] == null)
var typed_enums: Array[E] = []
typed_enums.resize(1)
- assert(str(typed_enums) == '[0]')
+ Utils.check(str(typed_enums) == '[0]')
typed_enums[0] = E.E0
- assert(str(typed_enums) == '[391]')
- assert(typed_enums.get_typed_builtin() == TYPE_INT)
+ Utils.check(str(typed_enums) == '[391]')
+ Utils.check(typed_enums.get_typed_builtin() == TYPE_INT)
const const_enums: Array[E] = []
- assert(const_enums.get_typed_builtin() == TYPE_INT)
- assert(const_enums.get_typed_class_name() == &'')
+ Utils.check(const_enums.get_typed_builtin() == TYPE_INT)
+ Utils.check(const_enums.get_typed_class_name() == &'')
var a := A.new()
var typed_natives: Array[RefCounted] = [a]
var typed_scripts = Array(typed_natives, TYPE_OBJECT, "RefCounted", A)
- assert(typed_scripts[0] == a)
+ Utils.check(typed_scripts[0] == a)
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.gd b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.gd
new file mode 100644
index 0000000000..95d497c3f3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.gd
@@ -0,0 +1,7 @@
+extends "./non_tool_extends_tool.notest.gd"
+
+class InnerClass extends "./non_tool_extends_tool.notest.gd":
+ pass
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.notest.gd b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.notest.gd
new file mode 100644
index 0000000000..07427846d1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.notest.gd
@@ -0,0 +1 @@
+@tool
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.out b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.out
new file mode 100644
index 0000000000..f65caf5222
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool.out
@@ -0,0 +1,9 @@
+GDTEST_OK
+>> WARNING
+>> Line: 1
+>> MISSING_TOOL
+>> The base class script has the "@tool" annotation, but this script does not have it.
+>> WARNING
+>> Line: 3
+>> MISSING_TOOL
+>> The base class script has the "@tool" annotation, but this script does not have it.
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.gd b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.gd
new file mode 100644
index 0000000000..a452307d99
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.gd
@@ -0,0 +1,9 @@
+@warning_ignore("missing_tool")
+extends "./non_tool_extends_tool.notest.gd"
+
+@warning_ignore("missing_tool")
+class InnerClass extends "./non_tool_extends_tool.notest.gd":
+ pass
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.out b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/non_tool_extends_tool_ignored.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/completion/index/array_type.cfg b/modules/gdscript/tests/scripts/completion/index/array_type.cfg
new file mode 100644
index 0000000000..5cd5565d00
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/array_type.cfg
@@ -0,0 +1,9 @@
+[output]
+include=[
+ {"display": "outer"},
+ {"display": "inner"},
+]
+exclude=[
+ {"display": "append"},
+ {"display": "\"append\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/index/array_type.gd b/modules/gdscript/tests/scripts/completion/index/array_type.gd
new file mode 100644
index 0000000000..e0a15da556
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/array_type.gd
@@ -0,0 +1,10 @@
+extends Node
+
+var outer
+
+func _ready() -> void:
+ var inner
+
+ var array: Array
+
+ array[i➡]
diff --git a/modules/gdscript/tests/scripts/completion/index/array_value.cfg b/modules/gdscript/tests/scripts/completion/index/array_value.cfg
new file mode 100644
index 0000000000..5cd5565d00
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/array_value.cfg
@@ -0,0 +1,9 @@
+[output]
+include=[
+ {"display": "outer"},
+ {"display": "inner"},
+]
+exclude=[
+ {"display": "append"},
+ {"display": "\"append\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/index/array_value.gd b/modules/gdscript/tests/scripts/completion/index/array_value.gd
new file mode 100644
index 0000000000..17451725bc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/array_value.gd
@@ -0,0 +1,10 @@
+extends Node
+
+var outer
+
+func _ready() -> void:
+ var inner
+
+ var array = []
+
+ array[i➡]
diff --git a/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.cfg b/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.cfg
new file mode 100644
index 0000000000..ecea284b5d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.cfg
@@ -0,0 +1,11 @@
+[output]
+include=[
+ {"display": "\"key1\""},
+ {"display": "\"key2\""},
+]
+exclude=[
+ {"display": "keys"},
+ {"display": "\"keys\""},
+ {"display": "key1"},
+ {"display": "key2"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.gd b/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.gd
new file mode 100644
index 0000000000..06498c57a6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.gd
@@ -0,0 +1,13 @@
+extends Node
+
+var outer
+
+const dict = {
+ "key1": "value",
+ "key2": null,
+}
+
+func _ready() -> void:
+ var inner
+
+ dict["➡"]
diff --git a/modules/gdscript/tests/scripts/completion/index/dictionary_type.cfg b/modules/gdscript/tests/scripts/completion/index/dictionary_type.cfg
new file mode 100644
index 0000000000..cddf7b8cc8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/dictionary_type.cfg
@@ -0,0 +1,9 @@
+[output]
+include=[
+ {"display": "outer"},
+ {"display": "inner"},
+]
+exclude=[
+ {"display": "keys"},
+ {"display": "\"keys\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/index/dictionary_type.gd b/modules/gdscript/tests/scripts/completion/index/dictionary_type.gd
new file mode 100644
index 0000000000..b02c62eea5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/dictionary_type.gd
@@ -0,0 +1,10 @@
+extends Node
+
+var outer
+
+func _ready() -> void:
+ var inner
+
+ var dict: Dictionary
+
+ dict[i➡]
diff --git a/modules/gdscript/tests/scripts/completion/index/dictionary_value.cfg b/modules/gdscript/tests/scripts/completion/index/dictionary_value.cfg
new file mode 100644
index 0000000000..cddf7b8cc8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/dictionary_value.cfg
@@ -0,0 +1,9 @@
+[output]
+include=[
+ {"display": "outer"},
+ {"display": "inner"},
+]
+exclude=[
+ {"display": "keys"},
+ {"display": "\"keys\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/index/dictionary_value.gd b/modules/gdscript/tests/scripts/completion/index/dictionary_value.gd
new file mode 100644
index 0000000000..60bf391716
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/dictionary_value.gd
@@ -0,0 +1,10 @@
+extends Node
+
+var outer
+
+func _ready() -> void:
+ var inner
+
+ var dict = {}
+
+ dict[i➡]
diff --git a/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.cfg b/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.cfg
new file mode 100644
index 0000000000..ecea284b5d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.cfg
@@ -0,0 +1,11 @@
+[output]
+include=[
+ {"display": "\"key1\""},
+ {"display": "\"key2\""},
+]
+exclude=[
+ {"display": "keys"},
+ {"display": "\"keys\""},
+ {"display": "key1"},
+ {"display": "key2"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.gd b/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.gd
new file mode 100644
index 0000000000..2220cdcc59
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.gd
@@ -0,0 +1,13 @@
+extends Node
+
+var outer
+
+func _ready() -> void:
+ var inner
+
+ var dict: Dictionary = {
+ "key1": "value",
+ "key2": null,
+ }
+
+ dict["➡"]
diff --git a/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.cfg b/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.cfg
new file mode 100644
index 0000000000..8da525bff8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.cfg
@@ -0,0 +1,9 @@
+[output]
+exclude=[
+ {"display": "keys"},
+ {"display": "\"keys\""},
+ {"display": "key1"},
+ {"display": "key2"},
+ {"display": "\"key1\""},
+ {"display": "\"key2\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.gd b/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.gd
new file mode 100644
index 0000000000..ba8d7f76fd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.gd
@@ -0,0 +1,13 @@
+extends Node
+
+var outer
+
+var dict = {
+ "key1": "value",
+ "key2": null,
+}
+
+func _ready() -> void:
+ var inner
+
+ dict["➡"]
diff --git a/modules/gdscript/tests/scripts/completion/index/untyped_local.cfg b/modules/gdscript/tests/scripts/completion/index/untyped_local.cfg
new file mode 100644
index 0000000000..1173043f94
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/untyped_local.cfg
@@ -0,0 +1,5 @@
+[output]
+include=[
+ {"display": "outer"},
+ {"display": "inner"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/index/untyped_local.gd b/modules/gdscript/tests/scripts/completion/index/untyped_local.gd
new file mode 100644
index 0000000000..1a1157af02
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/untyped_local.gd
@@ -0,0 +1,10 @@
+extends Node
+
+var outer
+
+func _ready() -> void:
+ var inner
+
+ var array
+
+ array[i➡]
diff --git a/modules/gdscript/tests/scripts/completion/index/untyped_property.cfg b/modules/gdscript/tests/scripts/completion/index/untyped_property.cfg
new file mode 100644
index 0000000000..1173043f94
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/untyped_property.cfg
@@ -0,0 +1,5 @@
+[output]
+include=[
+ {"display": "outer"},
+ {"display": "inner"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/index/untyped_property.gd b/modules/gdscript/tests/scripts/completion/index/untyped_property.gd
new file mode 100644
index 0000000000..9fa23da504
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/index/untyped_property.gd
@@ -0,0 +1,9 @@
+extends Node
+
+var outer
+var array
+
+func _ready() -> void:
+ var inner
+
+ array[i➡]
diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.gd b/modules/gdscript/tests/scripts/parser/features/annotations.gd
index 7a7d6d953e..1e5d3fdcad 100644
--- a/modules/gdscript/tests/scripts/parser/features/annotations.gd
+++ b/modules/gdscript/tests/scripts/parser/features/annotations.gd
@@ -1,7 +1,5 @@
extends Node
-const Utils = preload("../../utils.notest.gd")
-
@export_enum("A", "B", "C") var test_1
@export_enum("A", "B", "C",) var test_2
diff --git a/modules/gdscript/tests/scripts/parser/features/class.gd b/modules/gdscript/tests/scripts/parser/features/class.gd
index af24b32322..482d04a63b 100644
--- a/modules/gdscript/tests/scripts/parser/features/class.gd
+++ b/modules/gdscript/tests/scripts/parser/features/class.gd
@@ -18,8 +18,8 @@ func test():
test_instance.number = 42
var test_sub = TestSub.new()
- assert(test_sub.number == 25) # From Test.
- assert(test_sub.other_string == "bye") # From TestSub.
+ Utils.check(test_sub.number == 25) # From Test.
+ Utils.check(test_sub.other_string == "bye") # From TestSub.
var _test_constructor = TestConstructor.new()
_test_constructor = TestConstructor.new(500)
diff --git a/modules/gdscript/tests/scripts/parser/features/export_arrays.gd b/modules/gdscript/tests/scripts/parser/features/export_arrays.gd
index 0d97135a7b..cfda255905 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_arrays.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_arrays.gd
@@ -1,5 +1,3 @@
-const Utils = preload("../../utils.notest.gd")
-
@export_dir var test_dir: Array[String]
@export_dir var test_dir_packed: PackedStringArray
@export_file var test_file: Array[String]
diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.gd b/modules/gdscript/tests/scripts/parser/features/export_enum.gd
index 7f0737f4db..d50f0b2528 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_enum.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_enum.gd
@@ -1,5 +1,3 @@
-const Utils = preload("../../utils.notest.gd")
-
@export_enum("Red", "Green", "Blue") var test_untyped
@export_enum("Red:10", "Green:20", "Blue:30") var test_with_values
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
index 8bcb2bcb9a..1e134d0e0e 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
@@ -1,7 +1,6 @@
class_name ExportVariableTest
extends Node
-const Utils = preload("../../utils.notest.gd")
const PreloadedGlobalClass = preload("./export_variable_global.notest.gd")
const PreloadedUnnamedClass = preload("./export_variable_unnamed.notest.gd")
diff --git a/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd b/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd
index 2fa45c1d7d..0ec118b6b7 100644
--- a/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd
+++ b/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd
@@ -9,5 +9,5 @@ func test():
j_string += str(j)
return j_string
i_string += lambda.call()
- assert(i_string == '0202')
+ Utils.check(i_string == '0202')
print('ok')
diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.gd b/modules/gdscript/tests/scripts/parser/features/truthiness.gd
index 9c67a152f5..736cda7f74 100644
--- a/modules/gdscript/tests/scripts/parser/features/truthiness.gd
+++ b/modules/gdscript/tests/scripts/parser/features/truthiness.gd
@@ -1,30 +1,32 @@
func test():
- # The assertions below should all evaluate to `true` for this test to pass.
- assert(true)
- assert(not false)
- assert(500)
- assert(not 0)
- assert(500.5)
- assert(not 0.0)
- assert("non-empty string")
- assert(["non-empty array"])
- assert({"non-empty": "dictionary"})
- assert(Vector2(1, 0))
- assert(Vector2i(-1, -1))
- assert(Vector3(0, 0, 0.0001))
- assert(Vector3i(0, 0, 10000))
+ # The checks below should all evaluate to `true` for this test to pass.
+ Utils.check(true)
+ Utils.check(not false)
+ Utils.check(500)
+ Utils.check(not 0)
+ Utils.check(500.5)
+ Utils.check(not 0.0)
+ Utils.check("non-empty string")
+ Utils.check(["non-empty array"])
+ Utils.check({"non-empty": "dictionary"})
+ Utils.check(Vector2(1, 0))
+ Utils.check(Vector2i(-1, -1))
+ Utils.check(Vector3(0, 0, 0.0001))
+ Utils.check(Vector3i(0, 0, 10000))
# Zero position is `true` only if the Rect2's size is non-zero.
- assert(Rect2(0, 0, 0, 1))
+ Utils.check(Rect2(0, 0, 0, 1))
# Zero size is `true` only if the position is non-zero.
- assert(Rect2(1, 1, 0, 0))
+ Utils.check(Rect2(1, 1, 0, 0))
# Zero position is `true` only if the Rect2's size is non-zero.
- assert(Rect2i(0, 0, 0, 1))
+ Utils.check(Rect2i(0, 0, 0, 1))
# Zero size is `true` only if the position is non-zero.
- assert(Rect2i(1, 1, 0, 0))
+ Utils.check(Rect2i(1, 1, 0, 0))
# A fully black color is only truthy if its alpha component is not equal to `1`.
- assert(Color(0, 0, 0, 0.5))
+ Utils.check(Color(0, 0, 0, 0.5))
+
+ print("ok")
diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.out b/modules/gdscript/tests/scripts/parser/features/truthiness.out
index 705524857b..1b47ed10dc 100644
--- a/modules/gdscript/tests/scripts/parser/features/truthiness.out
+++ b/modules/gdscript/tests/scripts/parser/features/truthiness.out
@@ -1,65 +1,2 @@
GDTEST_OK
->> WARNING
->> Line: 3
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 4
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 5
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 6
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 7
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 8
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 9
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 12
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 13
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 14
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 15
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 18
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 21
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 24
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 27
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
->> WARNING
->> Line: 30
->> ASSERT_ALWAYS_TRUE
->> Assert statement is redundant because the expression is always true.
+ok
diff --git a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd
index bd38259cec..6eec37d64d 100644
--- a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd
@@ -25,7 +25,7 @@ func test():
print("String in Array[StringName]: ", "abc" in stringname_array)
var packed_string_array: PackedStringArray = []
- assert(!packed_string_array.push_back("abc"))
+ Utils.check(!packed_string_array.push_back("abc"))
print("StringName in PackedStringArray: ", &"abc" in packed_string_array)
string_array.push_back("abc")
diff --git a/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd b/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd
index d1746979be..6aa863c05f 100644
--- a/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd
@@ -1,10 +1,9 @@
const array: Array = [0]
const dictionary := {1: 2}
-@warning_ignore("assert_always_true")
func test():
- assert(array.is_read_only() == true)
- assert(str(array) == '[0]')
- assert(dictionary.is_read_only() == true)
- assert(str(dictionary) == '{ 1: 2 }')
+ Utils.check(array.is_read_only() == true)
+ Utils.check(str(array) == '[0]')
+ Utils.check(dictionary.is_read_only() == true)
+ Utils.check(str(dictionary) == '{ 1: 2 }')
print('ok')
diff --git a/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd b/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd
index a778fb1a94..0f2526667d 100644
--- a/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd
@@ -2,8 +2,8 @@ class Foo extends Node:
func _init():
name = 'f'
var string: String = name
- assert(typeof(string) == TYPE_STRING)
- assert(string == 'f')
+ Utils.check(typeof(string) == TYPE_STRING)
+ Utils.check(string == 'f')
print('ok')
func test():
diff --git a/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd
index 0851d939dc..9e67e75140 100644
--- a/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd
@@ -6,15 +6,15 @@ extends Node
@onready var later_untyped = [1]
func test():
- assert(typeof(later_inferred) == TYPE_ARRAY)
- assert(later_inferred.size() == 0)
+ Utils.check(typeof(later_inferred) == TYPE_ARRAY)
+ Utils.check(later_inferred.size() == 0)
- assert(typeof(later_static) == TYPE_ARRAY)
- assert(later_static.size() == 0)
+ Utils.check(typeof(later_static) == TYPE_ARRAY)
+ Utils.check(later_static.size() == 0)
- assert(typeof(later_static_with_init) == TYPE_ARRAY)
- assert(later_static_with_init.size() == 0)
+ Utils.check(typeof(later_static_with_init) == TYPE_ARRAY)
+ Utils.check(later_static_with_init.size() == 0)
- assert(typeof(later_untyped) == TYPE_NIL)
+ Utils.check(typeof(later_untyped) == TYPE_NIL)
print("ok")
diff --git a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd
index 0133d7fcfc..90df98e05b 100644
--- a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd
@@ -1,5 +1,3 @@
-const Utils = preload("../../utils.notest.gd")
-
# GH-73843
@export_group("Resource")
diff --git a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd
index 4cb51f8512..48a9349bf8 100644
--- a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd
@@ -62,7 +62,7 @@ func test():
0 when side_effect():
print("will run the side effect call, but not this")
_:
- assert(global == 1)
+ Utils.check(global == 1)
print("side effect only ran once")
func side_effect():
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd
index 42b29eee43..91d5a501c8 100644
--- a/modules/gdscript/tests/scripts/runtime/features/member_info.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd
@@ -5,8 +5,6 @@ class MyClass:
enum MyEnum {}
-const Utils = preload("../../utils.notest.gd")
-
static var test_static_var_untyped
static var test_static_var_weak_null = null
static var test_static_var_weak_int = 1
diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd
index ee5c1e1267..4ddbeaec0b 100644
--- a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd
@@ -1,7 +1,5 @@
# GH-82169
-const Utils = preload("../../utils.notest.gd")
-
class A:
static var test_static_var_a1
static var test_static_var_a2
diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.gd b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd
index fd23ea0db5..d6847768e6 100644
--- a/modules/gdscript/tests/scripts/runtime/features/metatypes.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd
@@ -3,7 +3,6 @@ class MyClass:
enum MyEnum {A, B, C}
-const Utils = preload("../../utils.notest.gd")
const Other = preload("./metatypes.notest.gd")
var test_native := JSON
diff --git a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd
index e1aba83507..dee36d3ae0 100644
--- a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd
@@ -6,6 +6,6 @@ class MyObj:
func test():
var obj_1 = MyObj.new()
var obj_2 = MyObj.new()
- assert(obj_2.get_reference_count() == 1)
+ Utils.check(obj_2.get_reference_count() == 1)
obj_1.set(&"obj", obj_2)
- assert(obj_2.get_reference_count() == 1)
+ Utils.check(obj_2.get_reference_count() == 1)
diff --git a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd
index b07c40b6da..11a670a7fb 100644
--- a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd
@@ -12,4 +12,4 @@ func test() -> void:
node1.add_child(node2)
add_child(node3)
- assert(get_node("_/Child") == $_/Child)
+ Utils.check(get_node("_/Child") == $_/Child)
diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd b/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd
index 691b611574..d72662736e 100644
--- a/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd
@@ -14,33 +14,33 @@ func test():
func test_construct(v, f):
@warning_ignore("unsafe_call_argument")
Vector2(v, v) # Built-in type construct.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_utility(v, f):
abs(v) # Utility function.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_builtin_call(v, f):
@warning_ignore("unsafe_method_access")
v.angle() # Built-in method call.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_builtin_call_validated(v: Vector2, f):
@warning_ignore("return_value_discarded")
v.abs() # Built-in method call validated.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_object_call(v, f):
@warning_ignore("unsafe_method_access")
v.get_reference_count() # Native type method call.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_object_call_method_bind(v: Resource, f):
@warning_ignore("return_value_discarded")
v.duplicate() # Native type method call with MethodBind.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
func test_object_call_method_bind_validated(v: RefCounted, f):
@warning_ignore("return_value_discarded")
v.get_reference_count() # Native type method call with validated MethodBind.
- assert(not f) # Test unary operator reading from `nil`.
+ Utils.check(not f) # Test unary operator reading from `nil`.
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd
index ec444b4ffa..859bfd7987 100644
--- a/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd
@@ -1,6 +1,6 @@
func test():
var untyped: Variant = 32
var typed: Array[int] = [untyped]
- assert(typed.get_typed_builtin() == TYPE_INT)
- assert(str(typed) == '[32]')
+ Utils.check(typed.get_typed_builtin() == TYPE_INT)
+ Utils.check(str(typed) == '[32]')
print('ok')
diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd
index 7fdd6556ec..5d615d8557 100644
--- a/modules/gdscript/tests/scripts/utils.notest.gd
+++ b/modules/gdscript/tests/scripts/utils.notest.gd
@@ -1,3 +1,13 @@
+class_name Utils
+
+
+# `assert()` is not evaluated in non-debug builds. Do not use `assert()`
+# for anything other than testing the `assert()` itself.
+static func check(condition: Variant) -> void:
+ if not condition:
+ printerr("Check failed.")
+
+
static func get_type(property: Dictionary, is_return: bool = false) -> String:
match property.type:
TYPE_NIL:
@@ -46,7 +56,7 @@ static func get_human_readable_hint_string(property: Dictionary) -> String:
while true:
if not hint_string.contains(":"):
- push_error("Invalid PROPERTY_HINT_TYPE_STRING format.")
+ printerr("Invalid PROPERTY_HINT_TYPE_STRING format.")
var elem_type_hint: String = hint_string.get_slice(":", 0)
hint_string = hint_string.substr(elem_type_hint.length() + 1)
@@ -58,7 +68,7 @@ static func get_human_readable_hint_string(property: Dictionary) -> String:
type_hint_prefixes += "<%s>:" % type_string(elem_type)
else:
if elem_type_hint.count("/") != 1:
- push_error("Invalid PROPERTY_HINT_TYPE_STRING format.")
+ printerr("Invalid PROPERTY_HINT_TYPE_STRING format.")
elem_type = elem_type_hint.get_slice("/", 0).to_int()
elem_hint = elem_type_hint.get_slice("/", 1).to_int()
type_hint_prefixes += "<%s>/<%s>:" % [
@@ -188,7 +198,7 @@ static func get_property_hint_name(hint: PropertyHint) -> String:
return "PROPERTY_HINT_HIDE_QUATERNION_EDIT"
PROPERTY_HINT_PASSWORD:
return "PROPERTY_HINT_PASSWORD"
- push_error("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.")
+ printerr("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.")
return "<invalid hint>"
@@ -240,7 +250,7 @@ static func get_property_usage_string(usage: int) -> String:
usage &= ~flag[0]
if usage != PROPERTY_USAGE_NONE:
- push_error("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.")
+ printerr("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.")
return "<invalid usage flags>"
return result.left(-1)
diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml
index ebeed015e9..10534594d3 100644
--- a/modules/gltf/doc_classes/GLTFDocument.xml
+++ b/modules/gltf/doc_classes/GLTFDocument.xml
@@ -63,6 +63,13 @@
The [param bake_fps] parameter overrides the bake_fps in [param state].
</description>
</method>
+ <method name="get_supported_gltf_extensions" qualifiers="static">
+ <return type="PackedStringArray" />
+ <description>
+ Returns a list of all support glTF extensions, including extensions supported directly by the engine, and extensions supported by user plugins registering [GLTFDocumentExtension] classes.
+ [b]Note:[/b] If this method is run before a GLTFDocumentExtension is registered, its extensions won't be included in the list. Be sure to only run this method after all extensions are registered. If you run this when the engine starts, consider waiting a frame before calling this method to ensure all extensions are registered.
+ </description>
+ </method>
<method name="register_gltf_document_extension" qualifiers="static">
<return type="void" />
<param index="0" name="extension" type="GLTFDocumentExtension" />
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index 0aab6e84b4..8e5a992bd4 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -336,7 +336,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
#endif
}
-Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
+Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option,
const HashMap<StringName, Variant> &p_options) {
if (p_path.get_extension().to_lower() != "blend") {
return true;
diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h
index 6adace9276..17eb9e5709 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.h
+++ b/modules/gltf/editor/editor_scene_importer_blend.h
@@ -74,7 +74,7 @@ public:
List<String> *r_missing_deps, Error *r_err = nullptr) override;
virtual void get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) override;
- virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
+ virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option,
const HashMap<StringName, Variant> &p_options) override;
};
diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp
index ce7e17d361..41e294cfc6 100644
--- a/modules/gltf/editor/editor_scene_importer_gltf.cpp
+++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp
@@ -100,7 +100,7 @@ void EditorSceneFormatImporterGLTF::handle_compatibility_options(HashMap<StringN
}
}
-Variant EditorSceneFormatImporterGLTF::get_option_visibility(const String &p_path, bool p_for_animation,
+Variant EditorSceneFormatImporterGLTF::get_option_visibility(const String &p_path, const String &p_scene_import_type,
const String &p_option, const HashMap<StringName, Variant> &p_options) {
return true;
}
diff --git a/modules/gltf/editor/editor_scene_importer_gltf.h b/modules/gltf/editor/editor_scene_importer_gltf.h
index d0a9aaf05a..e17b6f3f2e 100644
--- a/modules/gltf/editor/editor_scene_importer_gltf.h
+++ b/modules/gltf/editor/editor_scene_importer_gltf.h
@@ -50,7 +50,7 @@ public:
virtual void get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) override;
virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override;
- virtual Variant get_option_visibility(const String &p_path, bool p_for_animation,
+ virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type,
const String &p_option, const HashMap<StringName, Variant> &p_options) override;
};
diff --git a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
index 676f764c11..64117349e0 100644
--- a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
+++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp
@@ -52,24 +52,24 @@ Error GLTFDocumentExtensionConvertImporterMesh::import_post(Ref<GLTFState> p_sta
while (!queue.is_empty()) {
List<Node *>::Element *E = queue.front();
Node *node = E->get();
- ImporterMeshInstance3D *mesh_3d = cast_to<ImporterMeshInstance3D>(node);
- if (mesh_3d) {
- MeshInstance3D *mesh_instance_node_3d = memnew(MeshInstance3D);
- Ref<ImporterMesh> mesh = mesh_3d->get_mesh();
+ ImporterMeshInstance3D *importer_mesh_3d = Object::cast_to<ImporterMeshInstance3D>(node);
+ if (importer_mesh_3d) {
+ Ref<ImporterMesh> mesh = importer_mesh_3d->get_mesh();
if (mesh.is_valid()) {
+ MeshInstance3D *mesh_instance_node_3d = memnew(MeshInstance3D);
Ref<ArrayMesh> array_mesh = mesh->get_mesh();
mesh_instance_node_3d->set_name(node->get_name());
- mesh_instance_node_3d->set_transform(mesh_3d->get_transform());
+ mesh_instance_node_3d->set_transform(importer_mesh_3d->get_transform());
mesh_instance_node_3d->set_mesh(array_mesh);
- mesh_instance_node_3d->set_skin(mesh_3d->get_skin());
- mesh_instance_node_3d->set_skeleton_path(mesh_3d->get_skeleton_path());
+ mesh_instance_node_3d->set_skin(importer_mesh_3d->get_skin());
+ mesh_instance_node_3d->set_skeleton_path(importer_mesh_3d->get_skeleton_path());
node->replace_by(mesh_instance_node_3d);
- _copy_meta(mesh_3d, mesh_instance_node_3d);
+ _copy_meta(importer_mesh_3d, mesh_instance_node_3d);
_copy_meta(mesh.ptr(), array_mesh.ptr());
delete_queue.push_back(node);
node = mesh_instance_node_3d;
} else {
- memdelete(mesh_instance_node_3d);
+ WARN_PRINT("glTF: ImporterMeshInstance3D does not have a valid mesh. This should not happen. Continuing anyway.");
}
}
int child_count = node->get_child_count();
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index cd25b93e6c..69973a34dd 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -69,6 +69,24 @@
#include <stdlib.h>
#include <cstdint>
+static void _attach_extras_to_meta(const Dictionary &p_extras, Ref<Resource> p_node) {
+ if (!p_extras.is_empty()) {
+ p_node->set_meta("extras", p_extras);
+ }
+}
+
+static void _attach_meta_to_extras(Ref<Resource> p_node, Dictionary &p_json) {
+ if (p_node->has_meta("extras")) {
+ Dictionary node_extras = p_node->get_meta("extras");
+ if (p_json.has("extras")) {
+ Dictionary extras = p_json["extras"];
+ extras.merge(node_extras);
+ } else {
+ p_json["extras"] = node_extras;
+ }
+ }
+}
+
static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) {
Ref<ImporterMesh> importer_mesh;
importer_mesh.instantiate();
@@ -101,6 +119,7 @@ static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) {
array, p_mesh->surface_get_blend_shape_arrays(surface_i), p_mesh->surface_get_lods(surface_i), mat,
mat_name, p_mesh->surface_get_format(surface_i));
}
+ importer_mesh->merge_meta_from(*p_mesh);
return importer_mesh;
}
@@ -458,7 +477,7 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) {
if (extensions.is_empty()) {
node.erase("extensions");
}
-
+ _attach_meta_to_extras(gltf_node, node);
nodes.push_back(node);
}
if (!nodes.is_empty()) {
@@ -624,6 +643,10 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> p_state) {
}
}
+ if (n.has("extras")) {
+ _attach_extras_to_meta(n["extras"], node);
+ }
+
if (n.has("children")) {
const Array &children = n["children"];
for (int j = 0; j < children.size(); j++) {
@@ -2727,6 +2750,8 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) {
Dictionary e;
e["targetNames"] = target_names;
+ gltf_mesh["extras"] = e;
+ _attach_meta_to_extras(import_mesh, gltf_mesh);
weights.resize(target_names.size());
for (int name_i = 0; name_i < target_names.size(); name_i++) {
@@ -2742,8 +2767,6 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) {
ERR_FAIL_COND_V(target_names.size() != weights.size(), FAILED);
- gltf_mesh["extras"] = e;
-
gltf_mesh["primitives"] = primitives;
meshes.push_back(gltf_mesh);
@@ -2776,6 +2799,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) {
Array primitives = d["primitives"];
const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary();
+ _attach_extras_to_meta(extras, mesh);
Ref<ImporterMesh> import_mesh;
import_mesh.instantiate();
String mesh_name = "mesh";
@@ -4170,6 +4194,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) {
}
d["extensions"] = extensions;
+ _attach_meta_to_extras(material, d);
materials.push_back(d);
}
if (!materials.size()) {
@@ -4372,6 +4397,10 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) {
}
}
}
+
+ if (material_dict.has("extras")) {
+ _attach_extras_to_meta(material_dict["extras"], material);
+ }
p_state->materials.push_back(material);
}
@@ -5161,6 +5190,7 @@ ImporterMeshInstance3D *GLTFDocument::_generate_mesh_instance(Ref<GLTFState> p_s
return mi;
}
mi->set_mesh(import_mesh);
+ import_mesh->merge_meta_from(*mesh);
return mi;
}
@@ -5285,6 +5315,7 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current,
gltf_root = current_node_i;
p_state->root_nodes.push_back(gltf_root);
}
+ gltf_node->merge_meta_from(p_current);
_create_gltf_node(p_state, p_current, current_node_i, p_gltf_parent, gltf_root, gltf_node);
for (int node_i = 0; node_i < p_current->get_child_count(); node_i++) {
_convert_scene_node(p_state, p_current->get_child(node_i), current_node_i, gltf_root);
@@ -5676,6 +5707,8 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn
current_node->set_transform(gltf_node->transform);
}
+ current_node->merge_meta_from(*gltf_node);
+
p_state->scene_nodes.insert(p_node_index, current_node);
for (int i = 0; i < gltf_node->children.size(); ++i) {
_generate_scene_node(p_state, gltf_node->children[i], current_node, p_scene_root);
@@ -7060,6 +7093,8 @@ void GLTFDocument::_bind_methods() {
&GLTFDocument::register_gltf_document_extension, DEFVAL(false));
ClassDB::bind_static_method("GLTFDocument", D_METHOD("unregister_gltf_document_extension", "extension"),
&GLTFDocument::unregister_gltf_document_extension);
+ ClassDB::bind_static_method("GLTFDocument", D_METHOD("get_supported_gltf_extensions"),
+ &GLTFDocument::get_supported_gltf_extensions);
}
void GLTFDocument::_build_parent_hierachy(Ref<GLTFState> p_state) {
@@ -7100,6 +7135,36 @@ Vector<Ref<GLTFDocumentExtension>> GLTFDocument::get_all_gltf_document_extension
return all_document_extensions;
}
+Vector<String> GLTFDocument::get_supported_gltf_extensions() {
+ HashSet<String> set = get_supported_gltf_extensions_hashset();
+ Vector<String> vec;
+ for (const String &s : set) {
+ vec.append(s);
+ }
+ vec.sort();
+ return vec;
+}
+
+HashSet<String> GLTFDocument::get_supported_gltf_extensions_hashset() {
+ HashSet<String> supported_extensions;
+ // If the extension is supported directly in GLTFDocument, list it here.
+ // Other built-in extensions are supported by GLTFDocumentExtension classes.
+ supported_extensions.insert("GODOT_single_root");
+ supported_extensions.insert("KHR_lights_punctual");
+ supported_extensions.insert("KHR_materials_emissive_strength");
+ supported_extensions.insert("KHR_materials_pbrSpecularGlossiness");
+ supported_extensions.insert("KHR_materials_unlit");
+ supported_extensions.insert("KHR_texture_transform");
+ for (Ref<GLTFDocumentExtension> ext : all_document_extensions) {
+ ERR_CONTINUE(ext.is_null());
+ Vector<String> ext_supported_extensions = ext->get_supported_extensions();
+ for (int i = 0; i < ext_supported_extensions.size(); ++i) {
+ supported_extensions.insert(ext_supported_extensions[i]);
+ }
+ }
+ return supported_extensions;
+}
+
PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref<GLTFState> p_state, Error *r_err) {
Error err = _encode_buffer_glb(p_state, "");
if (r_err) {
@@ -7452,19 +7517,7 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) {
Vector<String> ext_array = p_state->json["extensionsRequired"];
p_state->extensions_required = ext_array;
}
- HashSet<String> supported_extensions;
- supported_extensions.insert("KHR_lights_punctual");
- supported_extensions.insert("KHR_materials_pbrSpecularGlossiness");
- supported_extensions.insert("KHR_texture_transform");
- supported_extensions.insert("KHR_materials_unlit");
- supported_extensions.insert("KHR_materials_emissive_strength");
- for (Ref<GLTFDocumentExtension> ext : document_extensions) {
- ERR_CONTINUE(ext.is_null());
- Vector<String> ext_supported_extensions = ext->get_supported_extensions();
- for (int i = 0; i < ext_supported_extensions.size(); ++i) {
- supported_extensions.insert(ext_supported_extensions[i]);
- }
- }
+ HashSet<String> supported_extensions = get_supported_gltf_extensions_hashset();
Error ret = OK;
for (int i = 0; i < p_state->extensions_required.size(); i++) {
if (!supported_extensions.has(p_state->extensions_required[i])) {
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index d37544750d..b3e6dcf54a 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -92,6 +92,8 @@ public:
static void unregister_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension);
static void unregister_all_gltf_document_extensions();
static Vector<Ref<GLTFDocumentExtension>> get_all_gltf_document_extensions();
+ static Vector<String> get_supported_gltf_extensions();
+ static HashSet<String> get_supported_gltf_extensions_hashset();
void set_naming_version(int p_version);
int get_naming_version() const;
diff --git a/modules/gltf/tests/test_gltf_extras.h b/modules/gltf/tests/test_gltf_extras.h
new file mode 100644
index 0000000000..96aadf3023
--- /dev/null
+++ b/modules/gltf/tests/test_gltf_extras.h
@@ -0,0 +1,165 @@
+/**************************************************************************/
+/* test_gltf_extras.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_GLTF_EXTRAS_H
+#define TEST_GLTF_EXTRAS_H
+
+#include "tests/test_macros.h"
+
+#ifdef TOOLS_ENABLED
+
+#include "core/os/os.h"
+#include "editor/import/3d/resource_importer_scene.h"
+#include "modules/gltf/editor/editor_scene_importer_gltf.h"
+#include "modules/gltf/gltf_document.h"
+#include "modules/gltf/gltf_state.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/main/window.h"
+#include "scene/resources/3d/primitive_meshes.h"
+#include "scene/resources/material.h"
+#include "scene/resources/packed_scene.h"
+
+namespace TestGltfExtras {
+
+static Node *_gltf_export_then_import(Node *p_root, String &p_tempfilebase) {
+ Ref<GLTFDocument> doc;
+ doc.instantiate();
+ Ref<GLTFState> state;
+ state.instantiate();
+ Error err = doc->append_from_scene(p_root, state, EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS);
+ CHECK_MESSAGE(err == OK, "GLTF state generation failed.");
+ err = doc->write_to_filesystem(state, p_tempfilebase + ".gltf");
+ CHECK_MESSAGE(err == OK, "Writing GLTF to cache dir failed.");
+
+ // Setting up importers.
+ Ref<ResourceImporterScene> import_scene = memnew(ResourceImporterScene("PackedScene", true));
+ ResourceFormatImporter::get_singleton()->add_importer(import_scene);
+ Ref<EditorSceneFormatImporterGLTF> import_gltf;
+ import_gltf.instantiate();
+ ResourceImporterScene::add_scene_importer(import_gltf);
+
+ // GTLF importer behaves differently outside of editor, it's too late to modify Engine::get_editor_hint
+ // as the registration of runtime extensions already happened, so remove them. See modules/gltf/register_types.cpp
+ GLTFDocument::unregister_all_gltf_document_extensions();
+
+ HashMap<StringName, Variant> options(20);
+ options["nodes/root_type"] = "";
+ options["nodes/root_name"] = "";
+ options["nodes/apply_root_scale"] = true;
+ options["nodes/root_scale"] = 1.0;
+ options["meshes/ensure_tangents"] = true;
+ options["meshes/generate_lods"] = false;
+ options["meshes/create_shadow_meshes"] = true;
+ options["meshes/light_baking"] = 1;
+ options["meshes/lightmap_texel_size"] = 0.2;
+ options["meshes/force_disable_compression"] = false;
+ options["skins/use_named_skins"] = true;
+ options["animation/import"] = true;
+ options["animation/fps"] = 30;
+ options["animation/trimming"] = false;
+ options["animation/remove_immutable_tracks"] = true;
+ options["import_script/path"] = "";
+ options["_subresources"] = Dictionary();
+ options["gltf/naming_version"] = 1;
+
+ // Process gltf file, note that this generates `.scn` resource from the 2nd argument.
+ err = import_scene->import(p_tempfilebase + ".gltf", p_tempfilebase, options, nullptr, nullptr, nullptr);
+ CHECK_MESSAGE(err == OK, "GLTF import failed.");
+ ResourceImporterScene::remove_scene_importer(import_gltf);
+
+ Ref<PackedScene> packed_scene = ResourceLoader::load(p_tempfilebase + ".scn", "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err);
+ CHECK_MESSAGE(err == OK, "Loading scene failed.");
+ Node *p_scene = packed_scene->instantiate();
+ return p_scene;
+}
+
+TEST_CASE("[SceneTree][Node] GLTF test mesh and material meta export and import") {
+ // Setup scene.
+ Ref<StandardMaterial3D> original_material = memnew(StandardMaterial3D);
+ original_material->set_albedo(Color(1.0, .0, .0));
+ original_material->set_name("material");
+ Dictionary material_dict;
+ material_dict["node_type"] = "material";
+ original_material->set_meta("extras", material_dict);
+
+ Ref<PlaneMesh> original_meshdata = memnew(PlaneMesh);
+ original_meshdata->set_name("planemesh");
+ Dictionary meshdata_dict;
+ meshdata_dict["node_type"] = "planemesh";
+ original_meshdata->set_meta("extras", meshdata_dict);
+ original_meshdata->surface_set_material(0, original_material);
+
+ MeshInstance3D *original_mesh_instance = memnew(MeshInstance3D);
+ original_mesh_instance->set_mesh(original_meshdata);
+ original_mesh_instance->set_name("mesh_instance_3d");
+ Dictionary mesh_instance_dict;
+ mesh_instance_dict["node_type"] = "mesh_instance_3d";
+ original_mesh_instance->set_meta("extras", mesh_instance_dict);
+
+ Node3D *original = memnew(Node3D);
+ SceneTree::get_singleton()->get_root()->add_child(original);
+ original->add_child(original_mesh_instance);
+ original->set_name("node3d");
+ Dictionary node_dict;
+ node_dict["node_type"] = "node3d";
+ original->set_meta("extras", node_dict);
+ original->set_meta("meta_not_nested_under_extras", "should not propagate");
+
+ // Convert to GLFT and back.
+ String tempfile = OS::get_singleton()->get_cache_path().path_join("gltf_extras");
+ Node *loaded = _gltf_export_then_import(original, tempfile);
+
+ // Compare the results.
+ CHECK(loaded->get_name() == "node3d");
+ CHECK(Dictionary(loaded->get_meta("extras")).size() == 1);
+ CHECK(Dictionary(loaded->get_meta("extras"))["node_type"] == "node3d");
+ CHECK_FALSE(loaded->has_meta("meta_not_nested_under_extras"));
+ CHECK_FALSE(Dictionary(loaded->get_meta("extras")).has("meta_not_nested_under_extras"));
+
+ MeshInstance3D *mesh_instance_3d = Object::cast_to<MeshInstance3D>(loaded->find_child("mesh_instance_3d", false, true));
+ CHECK(mesh_instance_3d->get_name() == "mesh_instance_3d");
+ CHECK(Dictionary(mesh_instance_3d->get_meta("extras"))["node_type"] == "mesh_instance_3d");
+
+ Ref<Mesh> mesh = mesh_instance_3d->get_mesh();
+ CHECK(Dictionary(mesh->get_meta("extras"))["node_type"] == "planemesh");
+
+ Ref<Material> material = mesh->surface_get_material(0);
+ CHECK(material->get_name() == "material");
+ CHECK(Dictionary(material->get_meta("extras"))["node_type"] == "material");
+
+ memdelete(original_mesh_instance);
+ memdelete(original);
+ memdelete(loaded);
+}
+} // namespace TestGltfExtras
+
+#endif // TOOLS_ENABLED
+
+#endif // TEST_GLTF_EXTRAS_H
diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp
index ddc51bcf6b..47a5b23895 100644
--- a/modules/lightmapper_rd/lightmapper_rd.cpp
+++ b/modules/lightmapper_rd/lightmapper_rd.cpp
@@ -1716,7 +1716,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
light_probe_buffer = rd->storage_buffer_create(sizeof(float) * 4 * 9 * probe_positions.size());
if (p_step_function) {
- p_step_function(0.7, RTR("Baking lightprobes"), p_bake_userdata, true);
+ p_step_function(0.7, RTR("Baking light probes"), p_bake_userdata, true);
}
Vector<RD::Uniform> uniforms;
diff --git a/modules/mbedtls/tls_context_mbedtls.cpp b/modules/mbedtls/tls_context_mbedtls.cpp
index eaea7b9293..f5c196596e 100644
--- a/modules/mbedtls/tls_context_mbedtls.cpp
+++ b/modules/mbedtls/tls_context_mbedtls.cpp
@@ -153,7 +153,7 @@ Error TLSContextMbedTLS::init_client(int p_transport, const String &p_hostname,
int authmode = MBEDTLS_SSL_VERIFY_REQUIRED;
bool unsafe = p_options->is_unsafe_client();
- if (unsafe && p_options->get_trusted_ca_chain().is_valid()) {
+ if (unsafe && p_options->get_trusted_ca_chain().is_null()) {
authmode = MBEDTLS_SSL_VERIFY_NONE;
}
diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp
index 5720f844bb..394213963a 100644
--- a/modules/minimp3/audio_stream_mp3.cpp
+++ b/modules/minimp3/audio_stream_mp3.cpp
@@ -57,7 +57,7 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) {
mp3dec_frame_info_t frame_info;
mp3d_sample_t *buf_frame = nullptr;
- int samples_mixed = mp3dec_ex_read_frame(mp3d, &buf_frame, &frame_info, mp3_stream->channels);
+ int samples_mixed = mp3dec_ex_read_frame(&mp3d, &buf_frame, &frame_info, mp3_stream->channels);
if (samples_mixed) {
p_buffer[p_frames - todo] = AudioFrame(buf_frame[0], buf_frame[samples_mixed - 1]);
@@ -70,7 +70,7 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) {
if (beat_loop && (int)frames_mixed >= beat_length_frames) {
for (int i = 0; i < FADE_SIZE; i++) {
- samples_mixed = mp3dec_ex_read_frame(mp3d, &buf_frame, &frame_info, mp3_stream->channels);
+ samples_mixed = mp3dec_ex_read_frame(&mp3d, &buf_frame, &frame_info, mp3_stream->channels);
loop_fade[i] = AudioFrame(buf_frame[0], buf_frame[samples_mixed - 1]);
if (!samples_mixed) {
break;
@@ -138,7 +138,7 @@ void AudioStreamPlaybackMP3::seek(double p_time) {
}
frames_mixed = uint32_t(mp3_stream->sample_rate * p_time);
- mp3dec_ex_seek(mp3d, (uint64_t)frames_mixed * mp3_stream->channels);
+ mp3dec_ex_seek(&mp3d, (uint64_t)frames_mixed * mp3_stream->channels);
}
void AudioStreamPlaybackMP3::tag_used_streams() {
@@ -181,10 +181,7 @@ Variant AudioStreamPlaybackMP3::get_parameter(const StringName &p_name) const {
}
AudioStreamPlaybackMP3::~AudioStreamPlaybackMP3() {
- if (mp3d) {
- mp3dec_ex_close(mp3d);
- memfree(mp3d);
- }
+ mp3dec_ex_close(&mp3d);
}
Ref<AudioStreamPlayback> AudioStreamMP3::instantiate_playback() {
@@ -197,9 +194,8 @@ Ref<AudioStreamPlayback> AudioStreamMP3::instantiate_playback() {
mp3s.instantiate();
mp3s->mp3_stream = Ref<AudioStreamMP3>(this);
- mp3s->mp3d = (mp3dec_ex_t *)memalloc(sizeof(mp3dec_ex_t));
- int errorcode = mp3dec_ex_open_buf(mp3s->mp3d, data.ptr(), data_len, MP3D_SEEK_TO_SAMPLE);
+ int errorcode = mp3dec_ex_open_buf(&mp3s->mp3d, data.ptr(), data_len, MP3D_SEEK_TO_SAMPLE);
mp3s->frames_mixed = 0;
mp3s->active = false;
@@ -224,15 +220,19 @@ void AudioStreamMP3::set_data(const Vector<uint8_t> &p_data) {
int src_data_len = p_data.size();
const uint8_t *src_datar = p_data.ptr();
- mp3dec_ex_t mp3d;
- int err = mp3dec_ex_open_buf(&mp3d, src_datar, src_data_len, MP3D_SEEK_TO_SAMPLE);
- ERR_FAIL_COND_MSG(err || mp3d.info.hz == 0, "Failed to decode mp3 file. Make sure it is a valid mp3 audio file.");
+ mp3dec_ex_t *mp3d = memnew(mp3dec_ex_t);
+ int err = mp3dec_ex_open_buf(mp3d, src_datar, src_data_len, MP3D_SEEK_TO_SAMPLE);
+ if (err || mp3d->info.hz == 0) {
+ memdelete(mp3d);
+ ERR_FAIL_MSG("Failed to decode mp3 file. Make sure it is a valid mp3 audio file.");
+ }
- channels = mp3d.info.channels;
- sample_rate = mp3d.info.hz;
- length = float(mp3d.samples) / (sample_rate * float(channels));
+ channels = mp3d->info.channels;
+ sample_rate = mp3d->info.hz;
+ length = float(mp3d->samples) / (sample_rate * float(channels));
- mp3dec_ex_close(&mp3d);
+ mp3dec_ex_close(mp3d);
+ memdelete(mp3d);
clear_data();
diff --git a/modules/minimp3/audio_stream_mp3.h b/modules/minimp3/audio_stream_mp3.h
index 81e8f8633c..39d389b8cd 100644
--- a/modules/minimp3/audio_stream_mp3.h
+++ b/modules/minimp3/audio_stream_mp3.h
@@ -49,7 +49,7 @@ class AudioStreamPlaybackMP3 : public AudioStreamPlaybackResampled {
bool looping_override = false;
bool looping = false;
- mp3dec_ex_t *mp3d = nullptr;
+ mp3dec_ex_t mp3d = {};
uint32_t frames_mixed = 0;
bool active = false;
int loops = 0;
diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
index b699765b8e..032d067ae4 100644
--- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
@@ -86,7 +86,7 @@ namespace GodotTools.BuildLogger
WriteLine(line);
- string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," +
+ string errorLine = $@"error,{e.File?.CsvEscape() ?? string.Empty},{e.LineNumber},{e.ColumnNumber}," +
$"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," +
$"{e.ProjectFile?.CsvEscape() ?? string.Empty}";
_issuesStreamWriter.WriteLine(errorLine);
@@ -101,7 +101,7 @@ namespace GodotTools.BuildLogger
WriteLine(line);
- string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," +
+ string warningLine = $@"warning,{e.File?.CsvEscape() ?? string.Empty},{e.LineNumber},{e.ColumnNumber}," +
$"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," +
$"{e.ProjectFile?.CsvEscape() ?? string.Empty}";
_issuesStreamWriter.WriteLine(warningLine);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
index ebb2677361..d7877fa5fc 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
@@ -265,11 +265,6 @@ namespace GodotTools.Build
success = Publish(buildInfo);
}
- if (!success)
- {
- ShowBuildErrorDialog("Failed to publish .NET project");
- }
-
return success;
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index ede0600ac1..a5f24fb67b 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -75,7 +75,19 @@ namespace GodotTools.Export
};
}
- private string? _maybeLastExportError;
+ private void AddExceptionMessage(EditorExportPlatform platform, Exception exception)
+ {
+ string? exceptionMessage = exception.Message;
+ if (string.IsNullOrEmpty(exceptionMessage))
+ {
+ exceptionMessage = $"Exception thrown: {exception.GetType().Name}";
+ }
+
+ platform.AddMessage(EditorExportPlatform.ExportMessageType.Error, "Export .NET Project", exceptionMessage);
+
+ // We also print exceptions as we receive them to stderr.
+ Console.Error.WriteLine(exception);
+ }
// With this method we can override how a file is exported in the PCK
public override void _ExportFile(string path, string type, string[] features)
@@ -92,8 +104,8 @@ namespace GodotTools.Export
if (!ProjectContainsDotNet())
{
- _maybeLastExportError = $"This project contains C# files but no solution file was found at the following path: {GodotSharpDirs.ProjectSlnPath}\n" +
- "A solution file is required for projects with C# files. Please ensure that the solution file exists in the specified location and try again.";
+ GetExportPlatform().AddMessage(EditorExportPlatform.ExportMessageType.Error, "Export .NET Project", $"This project contains C# files but no solution file was found at the following path: {GodotSharpDirs.ProjectSlnPath}\n" +
+ "A solution file is required for projects with C# files. Please ensure that the solution file exists in the specified location and try again.");
throw new InvalidOperationException($"{path} is a C# file but no solution file exists.");
}
@@ -124,16 +136,7 @@ namespace GodotTools.Export
}
catch (Exception e)
{
- _maybeLastExportError = e.Message;
-
- // 'maybeLastExportError' cannot be null or empty if there was an error, so we
- // must consider the possibility of exceptions being thrown without a message.
- if (string.IsNullOrEmpty(_maybeLastExportError))
- _maybeLastExportError = $"Exception thrown: {e.GetType().Name}";
-
- GD.PushError($"Failed to export project: {_maybeLastExportError}");
- Console.Error.WriteLine(e);
- // TODO: Do something on error once _ExportBegin supports failing.
+ AddExceptionMessage(GetExportPlatform(), e);
}
}
@@ -144,7 +147,9 @@ namespace GodotTools.Export
if (!ProjectContainsDotNet())
return;
- if (!DeterminePlatformFromFeatures(features, out string? platform))
+ string osName = GetExportPlatform().GetOsName();
+
+ if (!TryDeterminePlatformFromOSName(osName, out string? platform))
throw new NotSupportedException("Target platform not supported.");
if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS }
@@ -445,25 +450,22 @@ namespace GodotTools.Export
Directory.Delete(folder, recursive: true);
}
_tempFolders.Clear();
-
- // TODO: The following is just a workaround until the export plugins can be made to abort with errors
-
- // We check for empty as well, because it's set to empty after hot-reloading
- if (!string.IsNullOrEmpty(_maybeLastExportError))
- {
- string lastExportError = _maybeLastExportError;
- _maybeLastExportError = null;
-
- GodotSharpEditor.Instance.ShowErrorDialog(lastExportError, "Failed to export C# project");
- }
}
- private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, [NotNullWhen(true)] out string? platform)
+ /// <summary>
+ /// Tries to determine the platform from the export preset's platform OS name.
+ /// </summary>
+ /// <param name="osName">Name of the export operating system.</param>
+ /// <param name="platform">Platform name for the recognized supported platform.</param>
+ /// <returns>
+ /// <see langword="true"/> when the platform OS name is recognized as a supported platform,
+ /// <see langword="false"/> otherwise.
+ /// </returns>
+ private static bool TryDeterminePlatformFromOSName(string osName, [NotNullWhen(true)] out string? platform)
{
- foreach (var feature in features)
+ if (OS.PlatformFeatureMap.TryGetValue(osName, out platform))
{
- if (OS.PlatformFeatureMap.TryGetValue(feature, out platform))
- return true;
+ return true;
}
platform = null;
diff --git a/modules/mono/editor/hostfxr_resolver.cpp b/modules/mono/editor/hostfxr_resolver.cpp
index 7fa482969e..9c37ac810a 100644
--- a/modules/mono/editor/hostfxr_resolver.cpp
+++ b/modules/mono/editor/hostfxr_resolver.cpp
@@ -216,6 +216,7 @@ bool get_default_installation_dir(String &r_dotnet_root) {
#endif
}
+#ifndef WINDOWS_ENABLED
bool get_install_location_from_file(const String &p_file_path, String &r_dotnet_root) {
Error err = OK;
Ref<FileAccess> f = FileAccess::open(p_file_path, FileAccess::READ, &err);
@@ -233,6 +234,7 @@ bool get_install_location_from_file(const String &p_file_path, String &r_dotnet_
r_dotnet_root = line;
return true;
}
+#endif
bool get_dotnet_self_registered_dir(String &r_dotnet_root) {
#if defined(WINDOWS_ENABLED)
@@ -260,7 +262,7 @@ bool get_dotnet_self_registered_dir(String &r_dotnet_root) {
return false;
}
- r_dotnet_root = String::utf16((const char16_t *)buffer.ptr());
+ r_dotnet_root = String::utf16((const char16_t *)buffer.ptr()).replace("\\", "/");
RegCloseKey(hkey);
return true;
#else
diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp
index 33b92f6266..78983187c7 100644
--- a/modules/navigation/2d/nav_mesh_generator_2d.cpp
+++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp
@@ -87,57 +87,55 @@ void NavMeshGenerator2D::sync() {
return;
}
- baking_navmesh_mutex.lock();
- generator_task_mutex.lock();
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ {
+ MutexLock generator_task_lock(generator_task_mutex);
- LocalVector<WorkerThreadPool::TaskID> finished_task_ids;
+ LocalVector<WorkerThreadPool::TaskID> finished_task_ids;
- for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) {
- if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) {
- WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
- finished_task_ids.push_back(E.key);
+ for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) {
+ if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+ finished_task_ids.push_back(E.key);
- NavMeshGeneratorTask2D *generator_task = E.value;
- DEV_ASSERT(generator_task->status == NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED);
+ NavMeshGeneratorTask2D *generator_task = E.value;
+ DEV_ASSERT(generator_task->status == NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED);
- baking_navmeshes.erase(generator_task->navigation_mesh);
- if (generator_task->callback.is_valid()) {
- generator_emit_callback(generator_task->callback);
+ baking_navmeshes.erase(generator_task->navigation_mesh);
+ if (generator_task->callback.is_valid()) {
+ generator_emit_callback(generator_task->callback);
+ }
+ memdelete(generator_task);
}
- memdelete(generator_task);
}
- }
- for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) {
- generator_tasks.erase(finished_task_id);
+ for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) {
+ generator_tasks.erase(finished_task_id);
+ }
}
-
- generator_task_mutex.unlock();
- baking_navmesh_mutex.unlock();
}
void NavMeshGenerator2D::cleanup() {
- baking_navmesh_mutex.lock();
- generator_task_mutex.lock();
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ {
+ MutexLock generator_task_lock(generator_task_mutex);
- baking_navmeshes.clear();
+ baking_navmeshes.clear();
- for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) {
- WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
- NavMeshGeneratorTask2D *generator_task = E.value;
- memdelete(generator_task);
- }
- generator_tasks.clear();
+ for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+ NavMeshGeneratorTask2D *generator_task = E.value;
+ memdelete(generator_task);
+ }
+ generator_tasks.clear();
- generator_rid_rwlock.write_lock();
- for (NavMeshGeometryParser2D *parser : generator_parsers) {
- generator_parser_owner.free(parser->self);
+ generator_rid_rwlock.write_lock();
+ for (NavMeshGeometryParser2D *parser : generator_parsers) {
+ generator_parser_owner.free(parser->self);
+ }
+ generator_parsers.clear();
+ generator_rid_rwlock.write_unlock();
}
- generator_parsers.clear();
- generator_rid_rwlock.write_unlock();
-
- generator_task_mutex.unlock();
- baking_navmesh_mutex.unlock();
}
void NavMeshGenerator2D::finish() {
@@ -212,7 +210,7 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPoly
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
- generator_task_mutex.lock();
+ MutexLock generator_task_lock(generator_task_mutex);
NavMeshGeneratorTask2D *generator_task = memnew(NavMeshGeneratorTask2D);
generator_task->navigation_mesh = p_navigation_mesh;
generator_task->source_geometry_data = p_source_geometry_data;
@@ -220,14 +218,11 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPoly
generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_STARTED;
generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator2D::generator_thread_bake, generator_task, NavMeshGenerator2D::baking_use_high_priority_threads, "NavMeshGeneratorBake2D");
generator_tasks.insert(generator_task->thread_task_id, generator_task);
- generator_task_mutex.unlock();
}
bool NavMeshGenerator2D::is_baking(Ref<NavigationPolygon> p_navigation_polygon) {
- baking_navmesh_mutex.lock();
- bool baking = baking_navmeshes.has(p_navigation_polygon);
- baking_navmesh_mutex.unlock();
- return baking;
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ return baking_navmeshes.has(p_navigation_polygon);
}
void NavMeshGenerator2D::generator_thread_bake(void *p_arg) {
diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp
index d17724baa0..e92a9d304b 100644
--- a/modules/navigation/3d/nav_mesh_generator_3d.cpp
+++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp
@@ -100,57 +100,55 @@ void NavMeshGenerator3D::sync() {
return;
}
- baking_navmesh_mutex.lock();
- generator_task_mutex.lock();
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ {
+ MutexLock generator_task_lock(generator_task_mutex);
- LocalVector<WorkerThreadPool::TaskID> finished_task_ids;
+ LocalVector<WorkerThreadPool::TaskID> finished_task_ids;
- for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) {
- if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) {
- WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
- finished_task_ids.push_back(E.key);
+ for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) {
+ if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+ finished_task_ids.push_back(E.key);
- NavMeshGeneratorTask3D *generator_task = E.value;
- DEV_ASSERT(generator_task->status == NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED);
+ NavMeshGeneratorTask3D *generator_task = E.value;
+ DEV_ASSERT(generator_task->status == NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED);
- baking_navmeshes.erase(generator_task->navigation_mesh);
- if (generator_task->callback.is_valid()) {
- generator_emit_callback(generator_task->callback);
+ baking_navmeshes.erase(generator_task->navigation_mesh);
+ if (generator_task->callback.is_valid()) {
+ generator_emit_callback(generator_task->callback);
+ }
+ memdelete(generator_task);
}
- memdelete(generator_task);
}
- }
- for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) {
- generator_tasks.erase(finished_task_id);
+ for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) {
+ generator_tasks.erase(finished_task_id);
+ }
}
-
- generator_task_mutex.unlock();
- baking_navmesh_mutex.unlock();
}
void NavMeshGenerator3D::cleanup() {
- baking_navmesh_mutex.lock();
- generator_task_mutex.lock();
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ {
+ MutexLock generator_task_lock(generator_task_mutex);
- baking_navmeshes.clear();
+ baking_navmeshes.clear();
- for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) {
- WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
- NavMeshGeneratorTask3D *generator_task = E.value;
- memdelete(generator_task);
- }
- generator_tasks.clear();
+ for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key);
+ NavMeshGeneratorTask3D *generator_task = E.value;
+ memdelete(generator_task);
+ }
+ generator_tasks.clear();
- generator_rid_rwlock.write_lock();
- for (NavMeshGeometryParser3D *parser : generator_parsers) {
- generator_parser_owner.free(parser->self);
+ generator_rid_rwlock.write_lock();
+ for (NavMeshGeometryParser3D *parser : generator_parsers) {
+ generator_parser_owner.free(parser->self);
+ }
+ generator_parsers.clear();
+ generator_rid_rwlock.write_unlock();
}
- generator_parsers.clear();
- generator_rid_rwlock.write_unlock();
-
- generator_task_mutex.unlock();
- baking_navmesh_mutex.unlock();
}
void NavMeshGenerator3D::finish() {
@@ -226,7 +224,7 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh
baking_navmeshes.insert(p_navigation_mesh);
baking_navmesh_mutex.unlock();
- generator_task_mutex.lock();
+ MutexLock generator_task_lock(generator_task_mutex);
NavMeshGeneratorTask3D *generator_task = memnew(NavMeshGeneratorTask3D);
generator_task->navigation_mesh = p_navigation_mesh;
generator_task->source_geometry_data = p_source_geometry_data;
@@ -234,14 +232,11 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh
generator_task->status = NavMeshGeneratorTask3D::TaskStatus::BAKING_STARTED;
generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator3D::generator_thread_bake, generator_task, NavMeshGenerator3D::baking_use_high_priority_threads, SNAME("NavMeshGeneratorBake3D"));
generator_tasks.insert(generator_task->thread_task_id, generator_task);
- generator_task_mutex.unlock();
}
bool NavMeshGenerator3D::is_baking(Ref<NavigationMesh> p_navigation_mesh) {
- baking_navmesh_mutex.lock();
- bool baking = baking_navmeshes.has(p_navigation_mesh);
- baking_navmesh_mutex.unlock();
- return baking;
+ MutexLock baking_navmesh_lock(baking_navmesh_mutex);
+ return baking_navmeshes.has(p_navigation_mesh);
}
void NavMeshGenerator3D::generator_thread_bake(void *p_arg) {
diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h
index 82e8854b7a..c18ee7155b 100644
--- a/modules/navigation/nav_map.h
+++ b/modules/navigation/nav_map.h
@@ -36,6 +36,7 @@
#include "core/math/math_defs.h"
#include "core/object/worker_thread_pool.h"
+#include "servers/navigation/navigation_globals.h"
#include <KdTree2d.h>
#include <KdTree3d.h>
@@ -55,21 +56,21 @@ class NavMap : public NavRid {
/// To find the polygons edges the vertices are displaced in a grid where
/// each cell has the following cell_size and cell_height.
- real_t cell_size = 0.25; // Must match ProjectSettings default 3D cell_size and NavigationMesh cell_size.
- real_t cell_height = 0.25; // Must match ProjectSettings default 3D cell_height and NavigationMesh cell_height.
+ real_t cell_size = NavigationDefaults3D::navmesh_cell_size;
+ real_t cell_height = NavigationDefaults3D::navmesh_cell_height;
// For the inter-region merging to work, internal rasterization is performed.
- float merge_rasterizer_cell_size = 0.25;
- float merge_rasterizer_cell_height = 0.25;
+ float merge_rasterizer_cell_size = NavigationDefaults3D::navmesh_cell_size;
+ float merge_rasterizer_cell_height = NavigationDefaults3D::navmesh_cell_height;
// This value is used to control sensitivity of internal rasterizer.
float merge_rasterizer_cell_scale = 1.0;
bool use_edge_connections = true;
/// This value is used to detect the near edges to connect.
- real_t edge_connection_margin = 0.25;
+ real_t edge_connection_margin = NavigationDefaults3D::edge_connection_margin;
/// This value is used to limit how far links search to find polygons to connect to.
- real_t link_connection_radius = 1.0;
+ real_t link_connection_radius = NavigationDefaults3D::link_connection_radius;
bool regenerate_polygons = true;
bool regenerate_links = true;
diff --git a/modules/openxr/doc_classes/OpenXRAPIExtension.xml b/modules/openxr/doc_classes/OpenXRAPIExtension.xml
index 4419d24dd3..432b331eec 100644
--- a/modules/openxr/doc_classes/OpenXRAPIExtension.xml
+++ b/modules/openxr/doc_classes/OpenXRAPIExtension.xml
@@ -17,12 +17,25 @@
<link title="XrPosef documentation">https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrPosef.html</link>
</tutorials>
<methods>
+ <method name="begin_debug_label_region">
+ <return type="void" />
+ <param index="0" name="label_name" type="String" />
+ <description>
+ Begins a new debug label region, this label will be reported in debug messages for any calls following this until [method end_debug_label_region] is called. Debug labels can be stacked.
+ </description>
+ </method>
<method name="can_render">
<return type="bool" />
<description>
Returns [code]true[/code] if OpenXR is initialized for rendering with an XR viewport.
</description>
</method>
+ <method name="end_debug_label_region">
+ <return type="void" />
+ <description>
+ Marks the end of a debug label region. Removes the latest debug label region added by calling [method begin_debug_label_region].
+ </description>
+ </method>
<method name="get_error_string">
<return type="String" />
<param index="0" name="result" type="int" />
@@ -88,6 +101,13 @@
Returns the id of the system, which is a [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrSystemId.html]XrSystemId[/url] cast to an integer.
</description>
</method>
+ <method name="insert_debug_label">
+ <return type="void" />
+ <param index="0" name="label_name" type="String" />
+ <description>
+ Inserts a debug label, this label is reported in any debug message resulting from the OpenXR calls that follows, until any of [method begin_debug_label_region], [method end_debug_label_region], or [method insert_debug_label] is called.
+ </description>
+ </method>
<method name="is_environment_blend_mode_alpha_supported">
<return type="int" enum="OpenXRAPIExtension.OpenXRAlphaBlendModeSupport" />
<description>
@@ -127,6 +147,15 @@
If set to [code]true[/code], an OpenXR extension is loaded which is capable of emulating the [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] blend mode.
</description>
</method>
+ <method name="set_object_name">
+ <return type="void" />
+ <param index="0" name="object_type" type="int" />
+ <param index="1" name="object_handle" type="int" />
+ <param index="2" name="object_name" type="String" />
+ <description>
+ Set the object name of an OpenXR object, used for debug output. [param object_type] must be a valid OpenXR [code]XrObjectType[/code] enum and [param object_handle] must be a valid OpenXR object handle.
+ </description>
+ </method>
<method name="transform_from_pose">
<return type="Transform3D" />
<param index="0" name="pose" type="const void*" />
diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_extension.cpp
index 994b08af53..8a448afc08 100644
--- a/modules/openxr/extensions/openxr_composition_layer_extension.cpp
+++ b/modules/openxr/extensions/openxr_composition_layer_extension.cpp
@@ -58,7 +58,7 @@ HashMap<String, bool *> OpenXRCompositionLayerExtension::get_requested_extension
return request_extensions;
}
-void OpenXRCompositionLayerExtension::on_session_created(const XrSession p_instance) {
+void OpenXRCompositionLayerExtension::on_session_created(const XrSession p_session) {
OpenXRAPI::get_singleton()->register_composition_layer_provider(this);
}
diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.h b/modules/openxr/extensions/openxr_composition_layer_extension.h
index 4fefc416e6..34e330a60a 100644
--- a/modules/openxr/extensions/openxr_composition_layer_extension.h
+++ b/modules/openxr/extensions/openxr_composition_layer_extension.h
@@ -49,7 +49,7 @@ public:
virtual ~OpenXRCompositionLayerExtension() override;
virtual HashMap<String, bool *> get_requested_extensions() override;
- virtual void on_session_created(const XrSession 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;
diff --git a/modules/openxr/extensions/openxr_debug_utils_extension.cpp b/modules/openxr/extensions/openxr_debug_utils_extension.cpp
new file mode 100644
index 0000000000..10dbe629f7
--- /dev/null
+++ b/modules/openxr/extensions/openxr_debug_utils_extension.cpp
@@ -0,0 +1,287 @@
+/**************************************************************************/
+/* openxr_debug_utils_extension.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "openxr_debug_utils_extension.h"
+
+#include "../openxr_api.h"
+#include "core/config/project_settings.h"
+#include "core/string/print_string.h"
+
+#include <openxr/openxr.h>
+
+OpenXRDebugUtilsExtension *OpenXRDebugUtilsExtension::singleton = nullptr;
+
+OpenXRDebugUtilsExtension *OpenXRDebugUtilsExtension::get_singleton() {
+ return singleton;
+}
+
+OpenXRDebugUtilsExtension::OpenXRDebugUtilsExtension() {
+ singleton = this;
+}
+
+OpenXRDebugUtilsExtension::~OpenXRDebugUtilsExtension() {
+ singleton = nullptr;
+}
+
+HashMap<String, bool *> OpenXRDebugUtilsExtension::get_requested_extensions() {
+ HashMap<String, bool *> request_extensions;
+
+ request_extensions[XR_EXT_DEBUG_UTILS_EXTENSION_NAME] = &debug_utils_ext;
+
+ return request_extensions;
+}
+
+void OpenXRDebugUtilsExtension::on_instance_created(const XrInstance p_instance) {
+ if (debug_utils_ext) {
+ EXT_INIT_XR_FUNC(xrCreateDebugUtilsMessengerEXT);
+ EXT_INIT_XR_FUNC(xrDestroyDebugUtilsMessengerEXT);
+ EXT_INIT_XR_FUNC(xrSetDebugUtilsObjectNameEXT);
+ EXT_INIT_XR_FUNC(xrSessionBeginDebugUtilsLabelRegionEXT);
+ EXT_INIT_XR_FUNC(xrSessionEndDebugUtilsLabelRegionEXT);
+ EXT_INIT_XR_FUNC(xrSessionInsertDebugUtilsLabelEXT);
+
+ debug_utils_ext = xrCreateDebugUtilsMessengerEXT_ptr && xrDestroyDebugUtilsMessengerEXT_ptr && xrSetDebugUtilsObjectNameEXT_ptr && xrSessionBeginDebugUtilsLabelRegionEXT_ptr && xrSessionEndDebugUtilsLabelRegionEXT_ptr && xrSessionInsertDebugUtilsLabelEXT_ptr;
+ } else {
+ WARN_PRINT("OpenXR: The debug utils extension is not available on this runtime. Debug logging is not enabled!");
+ }
+
+ // On successful init, setup our default messenger.
+ if (debug_utils_ext) {
+ int max_severity = GLOBAL_GET("xr/openxr/extensions/debug_utils");
+ int types = GLOBAL_GET("xr/openxr/extensions/debug_message_types");
+
+ XrDebugUtilsMessageSeverityFlagsEXT message_severities = 0;
+
+ if (max_severity >= 1) {
+ message_severities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
+ }
+ if (max_severity >= 2) {
+ message_severities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT;
+ }
+ if (max_severity >= 3) {
+ message_severities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT;
+ }
+ if (max_severity >= 4) {
+ message_severities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT;
+ }
+
+ XrDebugUtilsMessageTypeFlagsEXT message_types = 0;
+
+ // These should match up but just to be safe and future proof...
+ if (types & 1) {
+ message_types |= XR_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT;
+ }
+ if (types & 2) {
+ message_types |= XR_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT;
+ }
+ if (types & 4) {
+ message_types |= XR_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
+ }
+ if (types & 8) {
+ message_types |= XR_DEBUG_UTILS_MESSAGE_TYPE_CONFORMANCE_BIT_EXT;
+ }
+
+ XrDebugUtilsMessengerCreateInfoEXT callback_info = {
+ XR_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, // type
+ nullptr, // next
+ message_severities, // messageSeverities
+ message_types, // messageTypes
+ &OpenXRDebugUtilsExtension::_debug_callback, // userCallback
+ nullptr, // userData
+ };
+
+ XrResult result = xrCreateDebugUtilsMessengerEXT(p_instance, &callback_info, &default_messenger);
+ if (XR_FAILED(result)) {
+ ERR_PRINT("OpenXR: Failed to create debug callback [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]");
+ }
+
+ set_object_name(XR_OBJECT_TYPE_INSTANCE, uint64_t(p_instance), "Main Godot OpenXR Instance");
+ }
+}
+
+void OpenXRDebugUtilsExtension::on_instance_destroyed() {
+ if (default_messenger != XR_NULL_HANDLE) {
+ XrResult result = xrDestroyDebugUtilsMessengerEXT(default_messenger);
+ if (XR_FAILED(result)) {
+ ERR_PRINT("OpenXR: Failed to destroy debug callback [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]");
+ }
+
+ default_messenger = XR_NULL_HANDLE;
+ }
+
+ xrCreateDebugUtilsMessengerEXT_ptr = nullptr;
+ xrDestroyDebugUtilsMessengerEXT_ptr = nullptr;
+ xrSetDebugUtilsObjectNameEXT_ptr = nullptr;
+ xrSessionBeginDebugUtilsLabelRegionEXT_ptr = nullptr;
+ xrSessionEndDebugUtilsLabelRegionEXT_ptr = nullptr;
+ xrSessionInsertDebugUtilsLabelEXT_ptr = nullptr;
+ debug_utils_ext = false;
+}
+
+bool OpenXRDebugUtilsExtension::get_active() {
+ return debug_utils_ext;
+}
+
+void OpenXRDebugUtilsExtension::set_object_name(XrObjectType p_object_type, uint64_t p_object_handle, const char *p_object_name) {
+ ERR_FAIL_COND(!debug_utils_ext);
+ ERR_FAIL_NULL(xrSetDebugUtilsObjectNameEXT_ptr);
+
+ const XrDebugUtilsObjectNameInfoEXT space_name_info = {
+ XR_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, // type
+ nullptr, // next
+ p_object_type, // objectType
+ p_object_handle, // objectHandle
+ p_object_name, // objectName
+ };
+
+ XrResult result = xrSetDebugUtilsObjectNameEXT_ptr(OpenXRAPI::get_singleton()->get_instance(), &space_name_info);
+ if (XR_FAILED(result)) {
+ ERR_PRINT("OpenXR: Failed to set object name [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]");
+ }
+}
+
+void OpenXRDebugUtilsExtension::begin_debug_label_region(const char *p_label_name) {
+ ERR_FAIL_COND(!debug_utils_ext);
+ ERR_FAIL_NULL(xrSessionBeginDebugUtilsLabelRegionEXT_ptr);
+
+ const XrDebugUtilsLabelEXT session_active_region_label = {
+ XR_TYPE_DEBUG_UTILS_LABEL_EXT, // type
+ NULL, // next
+ p_label_name, // labelName
+ };
+
+ XrResult result = xrSessionBeginDebugUtilsLabelRegionEXT_ptr(OpenXRAPI::get_singleton()->get_session(), &session_active_region_label);
+ if (XR_FAILED(result)) {
+ ERR_PRINT("OpenXR: Failed to begin label region [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]");
+ }
+}
+
+void OpenXRDebugUtilsExtension::end_debug_label_region() {
+ ERR_FAIL_COND(!debug_utils_ext);
+ ERR_FAIL_NULL(xrSessionEndDebugUtilsLabelRegionEXT_ptr);
+
+ XrResult result = xrSessionEndDebugUtilsLabelRegionEXT_ptr(OpenXRAPI::get_singleton()->get_session());
+ if (XR_FAILED(result)) {
+ ERR_PRINT("OpenXR: Failed to end label region [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]");
+ }
+}
+
+void OpenXRDebugUtilsExtension::insert_debug_label(const char *p_label_name) {
+ ERR_FAIL_COND(!debug_utils_ext);
+ ERR_FAIL_NULL(xrSessionInsertDebugUtilsLabelEXT_ptr);
+
+ const XrDebugUtilsLabelEXT session_active_region_label = {
+ XR_TYPE_DEBUG_UTILS_LABEL_EXT, // type
+ NULL, // next
+ p_label_name, // labelName
+ };
+
+ XrResult result = xrSessionInsertDebugUtilsLabelEXT_ptr(OpenXRAPI::get_singleton()->get_session(), &session_active_region_label);
+ if (XR_FAILED(result)) {
+ ERR_PRINT("OpenXR: Failed to insert label [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]");
+ }
+}
+
+XrBool32 XRAPI_PTR OpenXRDebugUtilsExtension::_debug_callback(XrDebugUtilsMessageSeverityFlagsEXT p_message_severity, XrDebugUtilsMessageTypeFlagsEXT p_message_types, const XrDebugUtilsMessengerCallbackDataEXT *p_callback_data, void *p_user_data) {
+ OpenXRDebugUtilsExtension *debug_utils = OpenXRDebugUtilsExtension::get_singleton();
+
+ if (debug_utils) {
+ return debug_utils->debug_callback(p_message_severity, p_message_types, p_callback_data, p_user_data);
+ }
+
+ return XR_FALSE;
+}
+
+XrBool32 OpenXRDebugUtilsExtension::debug_callback(XrDebugUtilsMessageSeverityFlagsEXT p_message_severity, XrDebugUtilsMessageTypeFlagsEXT p_message_types, const XrDebugUtilsMessengerCallbackDataEXT *p_callback_data, void *p_user_data) {
+ String msg;
+
+ ERR_FAIL_NULL_V(p_callback_data, XR_FALSE);
+
+ if (p_message_types == XR_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) {
+ msg = ", type: General";
+ } else if (p_message_types == XR_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) {
+ msg = ", type: Validation";
+ } else if (p_message_types == XR_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) {
+ msg = ", type: Performance";
+ } else if (p_message_types == XR_DEBUG_UTILS_MESSAGE_TYPE_CONFORMANCE_BIT_EXT) {
+ msg = ", type: Conformance";
+ } else {
+ msg = ", type: Unknown (" + String::num_uint64(p_message_types) + ")";
+ }
+
+ if (p_callback_data->functionName) {
+ msg += ", function Name: " + String(p_callback_data->functionName);
+ }
+ if (p_callback_data->messageId) {
+ msg += "\nMessage ID: " + String(p_callback_data->messageId);
+ }
+ if (p_callback_data->message) {
+ msg += "\nMessage: " + String(p_callback_data->message);
+ }
+
+ if (p_callback_data->objectCount > 0) {
+ String objects;
+
+ for (uint32_t i = 0; i < p_callback_data->objectCount; i++) {
+ if (!objects.is_empty()) {
+ objects += ", ";
+ }
+ objects += p_callback_data->objects[i].objectName;
+ }
+
+ msg += "\nObjects: " + objects;
+ }
+
+ if (p_callback_data->sessionLabelCount > 0) {
+ String labels;
+
+ for (uint32_t i = 0; i < p_callback_data->sessionLabelCount; i++) {
+ if (!labels.is_empty()) {
+ labels += ", ";
+ }
+ labels += p_callback_data->sessionLabels[i].labelName;
+ }
+
+ msg += "\nLabels: " + labels;
+ }
+
+ if (p_message_severity == XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
+ ERR_PRINT("OpenXR: Severity: Error" + msg);
+ } else if (p_message_severity == XR_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
+ WARN_PRINT("OpenXR: Severity: Warning" + msg);
+ } else if (p_message_severity == XR_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) {
+ print_line("OpenXR: Severity: Info" + msg);
+ } else if (p_message_severity == XR_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) {
+ // This is a bit double because we won't output this unless verbose messaging in Godot is on.
+ print_verbose("OpenXR: Severity: Verbose" + msg);
+ }
+
+ return XR_FALSE;
+}
diff --git a/modules/openxr/extensions/openxr_debug_utils_extension.h b/modules/openxr/extensions/openxr_debug_utils_extension.h
new file mode 100644
index 0000000000..1ee4c2a101
--- /dev/null
+++ b/modules/openxr/extensions/openxr_debug_utils_extension.h
@@ -0,0 +1,76 @@
+/**************************************************************************/
+/* openxr_debug_utils_extension.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef OPENXR_DEBUG_UTILS_EXTENSION_H
+#define OPENXR_DEBUG_UTILS_EXTENSION_H
+
+#include "../util.h"
+#include "openxr_extension_wrapper.h"
+
+class OpenXRDebugUtilsExtension : public OpenXRExtensionWrapper {
+public:
+ static OpenXRDebugUtilsExtension *get_singleton();
+
+ OpenXRDebugUtilsExtension();
+ virtual ~OpenXRDebugUtilsExtension() override;
+
+ virtual HashMap<String, bool *> get_requested_extensions() override;
+ virtual void on_instance_created(const XrInstance p_instance) override;
+ virtual void on_instance_destroyed() override;
+
+ bool get_active();
+
+ void set_object_name(XrObjectType p_object_type, uint64_t p_object_handle, const char *p_object_name);
+ void begin_debug_label_region(const char *p_label_name);
+ void end_debug_label_region();
+ void insert_debug_label(const char *p_label_name);
+
+private:
+ static OpenXRDebugUtilsExtension *singleton;
+
+ // related extensions
+ bool debug_utils_ext = false;
+
+ // debug handlers
+ XrDebugUtilsMessengerEXT default_messenger = XR_NULL_HANDLE;
+
+ static XrBool32 XRAPI_PTR _debug_callback(XrDebugUtilsMessageSeverityFlagsEXT p_message_severity, XrDebugUtilsMessageTypeFlagsEXT p_message_types, const XrDebugUtilsMessengerCallbackDataEXT *p_callback_data, void *p_user_data);
+ XrBool32 debug_callback(XrDebugUtilsMessageSeverityFlagsEXT p_message_severity, XrDebugUtilsMessageTypeFlagsEXT p_message_types, const XrDebugUtilsMessengerCallbackDataEXT *p_callback_data, void *p_user_data);
+
+ // OpenXR API call wrappers
+ EXT_PROTO_XRRESULT_FUNC3(xrCreateDebugUtilsMessengerEXT, (XrInstance), p_instance, (const XrDebugUtilsMessengerCreateInfoEXT *), p_create_info, (XrDebugUtilsMessengerEXT *), p_messenger)
+ EXT_PROTO_XRRESULT_FUNC1(xrDestroyDebugUtilsMessengerEXT, (XrDebugUtilsMessengerEXT), p_messenger)
+ EXT_PROTO_XRRESULT_FUNC2(xrSetDebugUtilsObjectNameEXT, (XrInstance), p_instance, (const XrDebugUtilsObjectNameInfoEXT *), p_name_info)
+ EXT_PROTO_XRRESULT_FUNC2(xrSessionBeginDebugUtilsLabelRegionEXT, (XrSession), p_session, (const XrDebugUtilsLabelEXT *), p_label_info)
+ EXT_PROTO_XRRESULT_FUNC1(xrSessionEndDebugUtilsLabelRegionEXT, (XrSession), p_session)
+ EXT_PROTO_XRRESULT_FUNC2(xrSessionInsertDebugUtilsLabelEXT, (XrSession), p_session, (const XrDebugUtilsLabelEXT *), p_label_info)
+};
+
+#endif // OPENXR_DEBUG_UTILS_EXTENSION_H
diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h
index 8d05657afc..09a9556dfa 100644
--- a/modules/openxr/extensions/openxr_extension_wrapper.h
+++ b/modules/openxr/extensions/openxr_extension_wrapper.h
@@ -76,7 +76,7 @@ public:
virtual void on_before_instance_created() {} // `on_before_instance_created` is called before we create our OpenXR instance.
virtual void on_instance_created(const XrInstance p_instance) {} // `on_instance_created` is called right after we've successfully created our OpenXR instance.
virtual void on_instance_destroyed() {} // `on_instance_destroyed` is called right before we destroy our OpenXR instance.
- virtual void on_session_created(const XrSession p_instance) {} // `on_session_created` is called right after we've successfully created our OpenXR session.
+ virtual void on_session_created(const XrSession p_session) {} // `on_session_created` is called right after we've successfully created our OpenXR session.
virtual void on_session_destroyed() {} // `on_session_destroyed` is called right before we destroy our OpenXR session.
// `on_process` is called as part of our OpenXR process handling,
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index e4ec318a42..ecf7c05789 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -54,6 +54,7 @@
#endif
#include "extensions/openxr_composition_layer_depth_extension.h"
+#include "extensions/openxr_debug_utils_extension.h"
#include "extensions/openxr_eye_gaze_interaction.h"
#include "extensions/openxr_fb_display_refresh_rate_extension.h"
#include "extensions/openxr_fb_foveation_extension.h"
@@ -316,6 +317,46 @@ String OpenXRAPI::get_swapchain_format_name(int64_t p_swapchain_format) const {
return String("Swapchain format ") + String::num_int64(int64_t(p_swapchain_format));
}
+void OpenXRAPI::set_object_name(XrObjectType p_object_type, uint64_t p_object_handle, const String &p_object_name) {
+ OpenXRDebugUtilsExtension *debug_utils = OpenXRDebugUtilsExtension::get_singleton();
+ if (!debug_utils || !debug_utils->get_active()) {
+ // Not enabled/active? Ignore.
+ return;
+ }
+
+ debug_utils->set_object_name(p_object_type, p_object_handle, p_object_name.utf8().get_data());
+}
+
+void OpenXRAPI::begin_debug_label_region(const String &p_label_name) {
+ OpenXRDebugUtilsExtension *debug_utils = OpenXRDebugUtilsExtension::get_singleton();
+ if (!debug_utils || !debug_utils->get_active()) {
+ // Not enabled/active? Ignore.
+ return;
+ }
+
+ debug_utils->begin_debug_label_region(p_label_name.utf8().get_data());
+}
+
+void OpenXRAPI::end_debug_label_region() {
+ OpenXRDebugUtilsExtension *debug_utils = OpenXRDebugUtilsExtension::get_singleton();
+ if (!debug_utils || !debug_utils->get_active()) {
+ // Not enabled/active? Ignore.
+ return;
+ }
+
+ debug_utils->end_debug_label_region();
+}
+
+void OpenXRAPI::insert_debug_label(const String &p_label_name) {
+ OpenXRDebugUtilsExtension *debug_utils = OpenXRDebugUtilsExtension::get_singleton();
+ if (!debug_utils || !debug_utils->get_active()) {
+ // Not enabled/active? Ignore.
+ return;
+ }
+
+ debug_utils->insert_debug_label(p_label_name.utf8().get_data());
+}
+
bool OpenXRAPI::load_layer_properties() {
// This queries additional layers that are available and can be initialized when we create our OpenXR instance
if (layer_properties != nullptr) {
@@ -826,6 +867,10 @@ bool OpenXRAPI::create_session() {
return false;
}
+ set_object_name(XR_OBJECT_TYPE_SESSION, uint64_t(session), "Main Godot OpenXR Session");
+
+ begin_debug_label_region("Godot session active");
+
for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
wrapper->on_session_created(session);
}
@@ -916,6 +961,8 @@ bool OpenXRAPI::setup_play_space() {
print_line("OpenXR: Failed to create LOCAL space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]");
will_emulate_local_floor = false;
}
+
+ set_object_name(XR_OBJECT_TYPE_SPACE, uint64_t(local_floor_emulation.local_space), "Emulation local space");
}
if (local_floor_emulation.stage_space == XR_NULL_HANDLE) {
@@ -931,6 +978,8 @@ bool OpenXRAPI::setup_play_space() {
print_line("OpenXR: Failed to create STAGE space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]");
will_emulate_local_floor = false;
}
+
+ set_object_name(XR_OBJECT_TYPE_SPACE, uint64_t(local_floor_emulation.stage_space), "Emulation stage space");
}
if (!will_emulate_local_floor) {
@@ -972,6 +1021,8 @@ bool OpenXRAPI::setup_play_space() {
play_space = new_play_space;
reference_space = new_reference_space;
+ set_object_name(XR_OBJECT_TYPE_SPACE, uint64_t(play_space), "Play space");
+
local_floor_emulation.enabled = will_emulate_local_floor;
local_floor_emulation.should_reset_floor_height = will_emulate_local_floor;
@@ -1007,6 +1058,8 @@ bool OpenXRAPI::setup_view_space() {
return false;
}
+ set_object_name(XR_OBJECT_TYPE_SPACE, uint64_t(view_space), "View space");
+
return true;
}
@@ -1181,6 +1234,8 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) {
if (!render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, color_swapchain_format, render_state.main_swapchain_size.width, render_state.main_swapchain_size.height, sample_count, view_count)) {
return false;
}
+
+ set_object_name(XR_OBJECT_TYPE_SWAPCHAIN, uint64_t(render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_swapchain()), "Main color swapchain");
}
// We create our depth swapchain if:
@@ -1191,6 +1246,8 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) {
if (!render_state.main_swapchains[OPENXR_SWAPCHAIN_DEPTH].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depth_swapchain_format, render_state.main_swapchain_size.width, render_state.main_swapchain_size.height, sample_count, view_count)) {
return false;
}
+
+ set_object_name(XR_OBJECT_TYPE_SWAPCHAIN, uint64_t(render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_swapchain()), "Main depth swapchain");
}
// We create our velocity swapchain if:
@@ -1309,6 +1366,8 @@ void OpenXRAPI::destroy_session() {
wrapper->on_session_destroyed();
}
+ end_debug_label_region();
+
xrDestroySession(session);
session = XR_NULL_HANDLE;
}
@@ -2215,6 +2274,9 @@ void OpenXRAPI::pre_render() {
}
}
+ // We should get our frame no from the rendering server, but this will do.
+ begin_debug_label_region(String("Session Frame ") + String::num_uint64(++render_state.frame));
+
// let's start our frame..
XrFrameBeginInfo frame_begin_info = {
XR_TYPE_FRAME_BEGIN_INFO, // type
@@ -2333,6 +2395,8 @@ void OpenXRAPI::end_frame() {
return;
}
+ end_debug_label_region(); // Session frame #
+
// neither eye is rendered
return;
}
@@ -2407,6 +2471,8 @@ void OpenXRAPI::end_frame() {
print_line("OpenXR: failed to end frame! [", get_error_string(result), "]");
return;
}
+
+ end_debug_label_region(); // Session frame #
}
float OpenXRAPI::get_display_refresh_rate() const {
@@ -2822,6 +2888,8 @@ RID OpenXRAPI::action_set_create(const String p_name, const String p_localized_n
return RID();
}
+ set_object_name(XR_OBJECT_TYPE_ACTION_SET, uint64_t(action_set.handle), p_name);
+
return action_set_owner.make_rid(action_set);
}
@@ -2997,6 +3065,8 @@ RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String
return RID();
}
+ set_object_name(XR_OBJECT_TYPE_ACTION, uint64_t(action.handle), p_name);
+
return action_owner.make_rid(action);
}
diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h
index 88455379b9..0d1e4eb414 100644
--- a/modules/openxr/openxr_api.h
+++ b/modules/openxr/openxr_api.h
@@ -336,6 +336,7 @@ private:
XrTime predicted_display_time = 0;
XrSpace play_space = XR_NULL_HANDLE;
double render_target_size_multiplier = 1.0;
+ uint64_t frame = 0;
uint32_t view_count = 0;
XrView *views = nullptr;
@@ -422,6 +423,10 @@ public:
XrResult get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr);
String get_error_string(XrResult result) const;
String get_swapchain_format_name(int64_t p_swapchain_format) const;
+ void set_object_name(XrObjectType p_object_type, uint64_t p_object_handle, const String &p_object_name);
+ void begin_debug_label_region(const String &p_label_name);
+ void end_debug_label_region();
+ void insert_debug_label(const String &p_label_name);
OpenXRInterface *get_xr_interface() const { return xr_interface; }
void set_xr_interface(OpenXRInterface *p_xr_interface);
diff --git a/modules/openxr/openxr_api_extension.cpp b/modules/openxr/openxr_api_extension.cpp
index a1744fa1db..f3bc178d3a 100644
--- a/modules/openxr/openxr_api_extension.cpp
+++ b/modules/openxr/openxr_api_extension.cpp
@@ -43,6 +43,10 @@ void OpenXRAPIExtension::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_instance_proc_addr", "name"), &OpenXRAPIExtension::get_instance_proc_addr);
ClassDB::bind_method(D_METHOD("get_error_string", "result"), &OpenXRAPIExtension::get_error_string);
ClassDB::bind_method(D_METHOD("get_swapchain_format_name", "swapchain_format"), &OpenXRAPIExtension::get_swapchain_format_name);
+ ClassDB::bind_method(D_METHOD("set_object_name", "object_type", "object_handle", "object_name"), &OpenXRAPIExtension::set_object_name);
+ ClassDB::bind_method(D_METHOD("begin_debug_label_region", "label_name"), &OpenXRAPIExtension::begin_debug_label_region);
+ ClassDB::bind_method(D_METHOD("end_debug_label_region"), &OpenXRAPIExtension::end_debug_label_region);
+ ClassDB::bind_method(D_METHOD("insert_debug_label", "label_name"), &OpenXRAPIExtension::insert_debug_label);
ClassDB::bind_method(D_METHOD("is_initialized"), &OpenXRAPIExtension::is_initialized);
ClassDB::bind_method(D_METHOD("is_running"), &OpenXRAPIExtension::is_running);
@@ -116,6 +120,30 @@ String OpenXRAPIExtension::get_swapchain_format_name(int64_t p_swapchain_format)
return OpenXRAPI::get_singleton()->get_swapchain_format_name(p_swapchain_format);
}
+void OpenXRAPIExtension::set_object_name(int64_t p_object_type, uint64_t p_object_handle, const String &p_object_name) {
+ ERR_FAIL_NULL(OpenXRAPI::get_singleton());
+
+ OpenXRAPI::get_singleton()->set_object_name(XrObjectType(p_object_type), p_object_handle, p_object_name);
+}
+
+void OpenXRAPIExtension::begin_debug_label_region(const String &p_label_name) {
+ ERR_FAIL_NULL(OpenXRAPI::get_singleton());
+
+ OpenXRAPI::get_singleton()->begin_debug_label_region(p_label_name);
+}
+
+void OpenXRAPIExtension::end_debug_label_region() {
+ ERR_FAIL_NULL(OpenXRAPI::get_singleton());
+
+ OpenXRAPI::get_singleton()->end_debug_label_region();
+}
+
+void OpenXRAPIExtension::insert_debug_label(const String &p_label_name) {
+ ERR_FAIL_NULL(OpenXRAPI::get_singleton());
+
+ OpenXRAPI::get_singleton()->insert_debug_label(p_label_name);
+}
+
bool OpenXRAPIExtension::is_initialized() {
ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), false);
return OpenXRAPI::get_singleton()->is_initialized();
diff --git a/modules/openxr/openxr_api_extension.h b/modules/openxr/openxr_api_extension.h
index cff2c4738e..1b88b418f6 100644
--- a/modules/openxr/openxr_api_extension.h
+++ b/modules/openxr/openxr_api_extension.h
@@ -64,6 +64,10 @@ public:
uint64_t get_instance_proc_addr(String p_name);
String get_error_string(uint64_t result);
String get_swapchain_format_name(int64_t p_swapchain_format);
+ void set_object_name(int64_t p_object_type, uint64_t p_object_handle, const String &p_object_name);
+ void begin_debug_label_region(const String &p_label_name);
+ void end_debug_label_region();
+ void insert_debug_label(const String &p_label_name);
bool is_initialized();
bool is_running();
diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp
index 61c294eff5..f3fda2517c 100644
--- a/modules/openxr/register_types.cpp
+++ b/modules/openxr/register_types.cpp
@@ -48,6 +48,7 @@
#include "extensions/openxr_composition_layer_depth_extension.h"
#include "extensions/openxr_composition_layer_extension.h"
+#include "extensions/openxr_debug_utils_extension.h"
#include "extensions/openxr_eye_gaze_interaction.h"
#include "extensions/openxr_fb_display_refresh_rate_extension.h"
#include "extensions/openxr_hand_interaction_extension.h"
@@ -133,6 +134,9 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) {
OpenXRAPI::register_extension_wrapper(memnew(OpenXRVisibilityMaskExtension));
// register gated extensions
+ if (int(GLOBAL_GET("xr/openxr/extensions/debug_utils")) > 0) {
+ OpenXRAPI::register_extension_wrapper(memnew(OpenXRDebugUtilsExtension));
+ }
if (GLOBAL_GET("xr/openxr/extensions/hand_tracking")) {
OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandTrackingExtension));
}
diff --git a/modules/regex/doc_classes/RegEx.xml b/modules/regex/doc_classes/RegEx.xml
index ab74fce3a9..e12dc43b6f 100644
--- a/modules/regex/doc_classes/RegEx.xml
+++ b/modules/regex/doc_classes/RegEx.xml
@@ -58,15 +58,17 @@
<method name="compile">
<return type="int" enum="Error" />
<param index="0" name="pattern" type="String" />
+ <param index="1" name="show_error" type="bool" default="true" />
<description>
- Compiles and assign the search pattern to use. Returns [constant OK] if the compilation is successful. If an error is encountered, details are printed to standard output and an error is returned.
+ Compiles and assign the search pattern to use. Returns [constant OK] if the compilation is successful. If compilation fails, returns [constant FAILED] and when [param show_error] is [code]true[/code], details are printed to standard output.
</description>
</method>
<method name="create_from_string" qualifiers="static">
<return type="RegEx" />
<param index="0" name="pattern" type="String" />
+ <param index="1" name="show_error" type="bool" default="true" />
<description>
- Creates and compiles a new [RegEx] object.
+ Creates and compiles a new [RegEx] object. See also [method compile].
</description>
</method>
<method name="get_group_count" qualifiers="const">
diff --git a/modules/regex/regex.compat.inc b/modules/regex/regex.compat.inc
new file mode 100644
index 0000000000..0c380655a4
--- /dev/null
+++ b/modules/regex/regex.compat.inc
@@ -0,0 +1,46 @@
+/**************************************************************************/
+/* regex.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
+
+Ref<RegEx> RegEx::_create_from_string_bind_compat_95212(const String &p_pattern) {
+ return create_from_string(p_pattern, true);
+}
+
+Error RegEx::_compile_bind_compat_95212(const String &p_pattern) {
+ return compile(p_pattern, true);
+}
+
+void RegEx::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_static_method("RegEx", D_METHOD("create_from_string", "pattern"), &RegEx::_create_from_string_bind_compat_95212);
+ ClassDB::bind_compatibility_method(D_METHOD("compile", "pattern"), &RegEx::_compile_bind_compat_95212);
+}
+
+#endif
diff --git a/modules/regex/regex.cpp b/modules/regex/regex.cpp
index 9f34a6ca6a..85c0b9ecad 100644
--- a/modules/regex/regex.cpp
+++ b/modules/regex/regex.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "regex.h"
+#include "regex.compat.inc"
#include "core/os/memory.h"
@@ -161,10 +162,10 @@ void RegEx::_pattern_info(uint32_t what, void *where) const {
pcre2_pattern_info_32((pcre2_code_32 *)code, what, where);
}
-Ref<RegEx> RegEx::create_from_string(const String &p_pattern) {
+Ref<RegEx> RegEx::create_from_string(const String &p_pattern, bool p_show_error) {
Ref<RegEx> ret;
ret.instantiate();
- ret->compile(p_pattern);
+ ret->compile(p_pattern, p_show_error);
return ret;
}
@@ -175,7 +176,7 @@ void RegEx::clear() {
}
}
-Error RegEx::compile(const String &p_pattern) {
+Error RegEx::compile(const String &p_pattern, bool p_show_error) {
pattern = p_pattern;
clear();
@@ -192,10 +193,12 @@ Error RegEx::compile(const String &p_pattern) {
pcre2_compile_context_free_32(cctx);
if (!code) {
- PCRE2_UCHAR32 buf[256];
- pcre2_get_error_message_32(err, buf, 256);
- String message = String::num(offset) + ": " + String((const char32_t *)buf);
- ERR_PRINT(message.utf8());
+ if (p_show_error) {
+ PCRE2_UCHAR32 buf[256];
+ pcre2_get_error_message_32(err, buf, 256);
+ String message = String::num(offset) + ": " + String((const char32_t *)buf);
+ ERR_PRINT(message.utf8());
+ }
return FAILED;
}
return OK;
@@ -395,10 +398,10 @@ RegEx::~RegEx() {
}
void RegEx::_bind_methods() {
- ClassDB::bind_static_method("RegEx", D_METHOD("create_from_string", "pattern"), &RegEx::create_from_string);
+ ClassDB::bind_static_method("RegEx", D_METHOD("create_from_string", "pattern", "show_error"), &RegEx::create_from_string, DEFVAL(true));
ClassDB::bind_method(D_METHOD("clear"), &RegEx::clear);
- ClassDB::bind_method(D_METHOD("compile", "pattern"), &RegEx::compile);
+ ClassDB::bind_method(D_METHOD("compile", "pattern", "show_error"), &RegEx::compile, DEFVAL(true));
ClassDB::bind_method(D_METHOD("search", "subject", "offset", "end"), &RegEx::search, DEFVAL(0), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("search_all", "subject", "offset", "end"), &RegEx::search_all, DEFVAL(0), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("sub", "subject", "replacement", "all", "offset", "end"), &RegEx::sub, DEFVAL(false), DEFVAL(0), DEFVAL(-1));
diff --git a/modules/regex/regex.h b/modules/regex/regex.h
index 13476d69de..cb8b0459ad 100644
--- a/modules/regex/regex.h
+++ b/modules/regex/regex.h
@@ -81,11 +81,17 @@ class RegEx : public RefCounted {
protected:
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ static Ref<RegEx> _create_from_string_bind_compat_95212(const String &p_pattern);
+ Error _compile_bind_compat_95212(const String &p_pattern);
+ static void _bind_compatibility_methods();
+#endif
+
public:
- static Ref<RegEx> create_from_string(const String &p_pattern);
+ static Ref<RegEx> create_from_string(const String &p_pattern, bool p_show_error = true);
void clear();
- Error compile(const String &p_pattern);
+ Error compile(const String &p_pattern, bool p_show_error = true);
Ref<RegExMatch> search(const String &p_subject, int p_offset = 0, int p_end = -1) const;
TypedArray<RegExMatch> search_all(const String &p_subject, int p_offset = 0, int p_end = -1) const;
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index d0c22e9e4d..4bf09d3c84 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -136,11 +136,12 @@ hb_position_t TextServerAdvanced::_bmp_get_glyph_h_advance(hb_font_t *p_font, vo
return 0;
}
- if (!bm_font->face->glyph_map.has(p_glyph)) {
+ HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph);
+ if (!E) {
return 0;
}
- return bm_font->face->glyph_map[p_glyph].advance.x * 64;
+ return E->value.advance.x * 64;
}
hb_position_t TextServerAdvanced::_bmp_get_glyph_v_advance(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_glyph, void *p_user_data) {
@@ -150,11 +151,12 @@ hb_position_t TextServerAdvanced::_bmp_get_glyph_v_advance(hb_font_t *p_font, vo
return 0;
}
- if (!bm_font->face->glyph_map.has(p_glyph)) {
+ HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph);
+ if (!E) {
return 0;
}
- return -bm_font->face->glyph_map[p_glyph].advance.y * 64;
+ return -E->value.advance.y * 64;
}
hb_position_t TextServerAdvanced::_bmp_get_glyph_h_kerning(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_left_glyph, hb_codepoint_t p_right_glyph, void *p_user_data) {
@@ -178,11 +180,12 @@ hb_bool_t TextServerAdvanced::_bmp_get_glyph_v_origin(hb_font_t *p_font, void *p
return false;
}
- if (!bm_font->face->glyph_map.has(p_glyph)) {
+ HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph);
+ if (!E) {
return false;
}
- *r_x = bm_font->face->glyph_map[p_glyph].advance.x * 32;
+ *r_x = E->value.advance.x * 32;
*r_y = -bm_font->face->ascent * 64;
return true;
@@ -195,14 +198,15 @@ hb_bool_t TextServerAdvanced::_bmp_get_glyph_extents(hb_font_t *p_font, void *p_
return false;
}
- if (!bm_font->face->glyph_map.has(p_glyph)) {
+ HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph);
+ if (!E) {
return false;
}
r_extents->x_bearing = 0;
r_extents->y_bearing = 0;
- r_extents->width = bm_font->face->glyph_map[p_glyph].rect.size.x * 64;
- r_extents->height = bm_font->face->glyph_map[p_glyph].rect.size.y * 64;
+ r_extents->width = E->value.rect.size.x * 64;
+ r_extents->height = E->value.rect.size.y * 64;
return true;
}
@@ -1188,18 +1192,21 @@ _FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_bitma
/* Font Cache */
/*************************************************************************/
-_FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph) const {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false);
+_FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const {
+ FontForSizeAdvanced *fd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size, fd), false);
int32_t glyph_index = p_glyph & 0xffffff; // Remove subpixel shifts.
- FontForSizeAdvanced *fd = p_font_data->cache[p_size];
- if (fd->glyph_map.has(p_glyph)) {
- return fd->glyph_map[p_glyph].found;
+ HashMap<int32_t, FontGlyph>::Iterator E = fd->glyph_map.find(p_glyph);
+ if (E) {
+ r_glyph = E->value;
+ return E->value.found;
}
if (glyph_index == 0) { // Non graphical or invalid glyph, do not render.
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return true;
}
@@ -1235,7 +1242,8 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data,
int error = FT_Load_Glyph(fd->face, glyph_index, flags);
if (error) {
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return false;
}
@@ -1339,17 +1347,22 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data,
cleanup_stroker:
FT_Stroker_Done(stroker);
}
- fd->glyph_map[p_glyph] = gl;
+ E = fd->glyph_map.insert(p_glyph, gl);
+ r_glyph = E->value;
return gl.found;
}
#endif
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return false;
}
-_FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size) const {
+_FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size) const {
ERR_FAIL_COND_V(p_size.x <= 0, false);
- if (p_font_data->cache.has(p_size)) {
+
+ HashMap<Vector2i, FontForSizeAdvanced *>::Iterator E = p_font_data->cache.find(p_size);
+ if (E) {
+ r_cache_for_size = E->value;
return true;
}
@@ -1840,7 +1853,8 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f
// Init bitmap font.
fd->hb_handle = _bmp_font_create(fd, nullptr);
}
- p_font_data->cache[p_size] = fd;
+ p_font_data->cache.insert(p_size, fd);
+ r_cache_for_size = fd;
return true;
}
@@ -1864,9 +1878,10 @@ hb_font_t *TextServerAdvanced::_font_get_hb_handle(const RID &p_font_rid, int64_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), nullptr);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), nullptr);
- return fd->cache[size]->hb_handle;
+ return ffsd->hb_handle;
}
RID TextServerAdvanced::_create_font() {
@@ -1989,7 +2004,8 @@ void TextServerAdvanced::_font_set_style(const RID &p_font_rid, BitField<FontSty
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->style_flags = p_style;
}
@@ -1999,7 +2015,8 @@ BitField<TextServer::FontStyle> TextServerAdvanced::_font_get_style(const RID &p
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
return fd->style_flags;
}
@@ -2009,7 +2026,8 @@ void TextServerAdvanced::_font_set_style_name(const RID &p_font_rid, const Strin
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->style_name = p_name;
}
@@ -2019,7 +2037,8 @@ String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String());
return fd->style_name;
}
@@ -2029,7 +2048,8 @@ void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weigh
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->weight = CLAMP(p_weight, 100, 999);
}
@@ -2039,7 +2059,8 @@ int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 400);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 400);
return fd->weight;
}
@@ -2049,7 +2070,8 @@ void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stre
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->stretch = CLAMP(p_stretch, 50, 200);
}
@@ -2059,7 +2081,8 @@ int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 100);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 100);
return fd->stretch;
}
@@ -2069,7 +2092,8 @@ void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_n
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->font_name = p_name;
}
@@ -2079,7 +2103,8 @@ String TextServerAdvanced::_font_get_name(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String());
return fd->font_name;
}
@@ -2089,9 +2114,10 @@ Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid)
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
- hb_face_t *hb_face = hb_font_get_face(fd->cache[size]->hb_handle);
+ hb_face_t *hb_face = hb_font_get_face(ffsd->hb_handle);
unsigned int num_entries = 0;
const hb_ot_name_entry_t *names = hb_ot_name_list_names(hb_face, &num_entries);
@@ -2599,8 +2625,9 @@ void TextServerAdvanced::_font_set_ascent(const RID &p_font_rid, int64_t p_size,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->ascent = p_ascent;
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->ascent = p_ascent;
}
double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const {
@@ -2610,18 +2637,19 @@ double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_siz
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->ascent * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->ascent * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->ascent * (double)p_size / (double)fd->fixed_size;
+ return ffsd->ascent * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->ascent * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->ascent * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->ascent;
+ return ffsd->ascent;
}
}
@@ -2631,8 +2659,9 @@ void TextServerAdvanced::_font_set_descent(const RID &p_font_rid, int64_t p_size
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->descent = p_descent;
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->descent = p_descent;
}
double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_size) const {
@@ -2642,18 +2671,19 @@ double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_si
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->descent * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->descent * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->descent * (double)p_size / (double)fd->fixed_size;
+ return ffsd->descent * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->descent * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->descent * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->descent;
+ return ffsd->descent;
}
}
@@ -2664,8 +2694,9 @@ void TextServerAdvanced::_font_set_underline_position(const RID &p_font_rid, int
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->underline_position = p_underline_position;
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->underline_position = p_underline_position;
}
double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const {
@@ -2675,18 +2706,19 @@ double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, i
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->underline_position * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->underline_position * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->underline_position * (double)p_size / (double)fd->fixed_size;
+ return ffsd->underline_position * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->underline_position * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->underline_position * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->underline_position;
+ return ffsd->underline_position;
}
}
@@ -2697,8 +2729,9 @@ void TextServerAdvanced::_font_set_underline_thickness(const RID &p_font_rid, in
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->underline_thickness = p_underline_thickness;
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->underline_thickness = p_underline_thickness;
}
double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const {
@@ -2708,18 +2741,19 @@ double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->underline_thickness * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->fixed_size;
+ return ffsd->underline_thickness * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->underline_thickness;
+ return ffsd->underline_thickness;
}
}
@@ -2730,13 +2764,15 @@ void TextServerAdvanced::_font_set_scale(const RID &p_font_rid, int64_t p_size,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
return; // Do not override scale for dynamic fonts, it's calculated automatically.
}
#endif
- fd->cache[size]->scale = p_scale;
+ ffsd->scale = p_scale;
}
double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size) const {
@@ -2746,18 +2782,19 @@ double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->scale * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->scale * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->scale * (double)p_size / (double)fd->fixed_size;
+ return ffsd->scale * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->scale * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->scale * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->scale / fd->cache[size]->oversampling;
+ return ffsd->scale / ffsd->oversampling;
}
}
@@ -2768,9 +2805,10 @@ int64_t TextServerAdvanced::_font_get_texture_count(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
- return fd->cache[size]->textures.size();
+ return ffsd->textures.size();
}
void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) {
@@ -2779,8 +2817,9 @@ void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->textures.clear();
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->textures.clear();
}
void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) {
@@ -2789,10 +2828,11 @@ void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- ERR_FAIL_INDEX(p_texture_index, fd->cache[size]->textures.size());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ERR_FAIL_INDEX(p_texture_index, ffsd->textures.size());
- fd->cache[size]->textures.remove_at(p_texture_index);
+ ffsd->textures.remove_at(p_texture_index);
}
void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) {
@@ -2802,13 +2842,14 @@ void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
ERR_FAIL_COND(p_texture_index < 0);
- if (p_texture_index >= fd->cache[size]->textures.size()) {
- fd->cache[size]->textures.resize(p_texture_index + 1);
+ if (p_texture_index >= ffsd->textures.size()) {
+ ffsd->textures.resize(p_texture_index + 1);
}
- ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index];
+ ShelfPackTexture &tex = ffsd->textures.write[p_texture_index];
tex.image = p_image;
tex.texture_w = p_image->get_width();
@@ -2829,10 +2870,11 @@ Ref<Image> TextServerAdvanced::_font_get_texture_image(const RID &p_font_rid, co
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Ref<Image>());
- ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), Ref<Image>());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Ref<Image>());
+ ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), Ref<Image>());
- const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index];
+ const ShelfPackTexture &tex = ffsd->textures[p_texture_index];
return tex.image;
}
@@ -2843,13 +2885,14 @@ void TextServerAdvanced::_font_set_texture_offsets(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
ERR_FAIL_COND(p_texture_index < 0);
- if (p_texture_index >= fd->cache[size]->textures.size()) {
- fd->cache[size]->textures.resize(p_texture_index + 1);
+ if (p_texture_index >= ffsd->textures.size()) {
+ ffsd->textures.resize(p_texture_index + 1);
}
- ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index];
+ ShelfPackTexture &tex = ffsd->textures.write[p_texture_index];
tex.shelves.clear();
for (int32_t i = 0; i < p_offsets.size(); i += 4) {
tex.shelves.push_back(Shelf(p_offsets[i], p_offsets[i + 1], p_offsets[i + 2], p_offsets[i + 3]));
@@ -2862,10 +2905,11 @@ PackedInt32Array TextServerAdvanced::_font_get_texture_offsets(const RID &p_font
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array());
- ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), PackedInt32Array());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array());
+ ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), PackedInt32Array());
- const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index];
+ const ShelfPackTexture &tex = ffsd->textures[p_texture_index];
PackedInt32Array ret;
ret.resize(tex.shelves.size() * 4);
@@ -2887,10 +2931,11 @@ PackedInt32Array TextServerAdvanced::_font_get_glyph_list(const RID &p_font_rid,
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array());
PackedInt32Array ret;
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map;
for (const KeyValue<int32_t, FontGlyph> &E : gl) {
ret.push_back(E.key);
}
@@ -2903,9 +2948,10 @@ void TextServerAdvanced::_font_clear_glyphs(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- fd->cache[size]->glyph_map.clear();
+ ffsd->glyph_map.clear();
}
void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) {
@@ -2914,9 +2960,10 @@ void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- fd->cache[size]->glyph_map.erase(p_glyph);
+ ffsd->glyph_map.erase(p_glyph);
}
double TextServerAdvanced::_get_extra_advance(RID p_font_rid, int p_font_size) const {
@@ -2940,22 +2987,22 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
Vector2 ea;
if (fd->embolden != 0.0) {
ea.x = fd->embolden * double(size.x) / 64.0;
@@ -2963,17 +3010,17 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64
double scale = _font_get_scale(p_font_rid, p_size);
if (fd->msdf) {
- return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->msdf_source_size;
+ return (fgl.advance + ea) * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->fixed_size;
+ return (fgl.advance + ea) * (double)p_size / (double)fd->fixed_size;
} else {
- return (gl[p_glyph | mod].advance + ea) * Math::round((double)p_size / (double)fd->fixed_size);
+ return (fgl.advance + ea) * Math::round((double)p_size / (double)fd->fixed_size);
}
} else if ((scale == 1.0) && ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE))) {
- return (gl[p_glyph | mod].advance + ea).round();
+ return (fgl.advance + ea).round();
} else {
- return gl[p_glyph | mod].advance + ea;
+ return fgl.advance + ea;
}
}
@@ -2984,12 +3031,13 @@ void TextServerAdvanced::_font_set_glyph_advance(const RID &p_font_rid, int64_t
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].advance = p_advance;
- gl[p_glyph].found = true;
+ fgl.advance = p_advance;
+ fgl.found = true;
}
Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -2999,32 +3047,32 @@ Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
if (fd->msdf) {
- return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->msdf_source_size;
+ return fgl.rect.position * (double)p_size.x / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->fixed_size;
+ return fgl.rect.position * (double)p_size.x / (double)fd->fixed_size;
} else {
- return gl[p_glyph | mod].rect.position * Math::round((double)p_size.x / (double)fd->fixed_size);
+ return fgl.rect.position * Math::round((double)p_size.x / (double)fd->fixed_size);
}
} else {
- return gl[p_glyph | mod].rect.position;
+ return fgl.rect.position;
}
}
@@ -3035,12 +3083,13 @@ void TextServerAdvanced::_font_set_glyph_offset(const RID &p_font_rid, const Vec
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].rect.position = p_offset;
- gl[p_glyph].found = true;
+ fgl.rect.position = p_offset;
+ fgl.found = true;
}
Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -3050,32 +3099,32 @@ Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
if (fd->msdf) {
- return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->msdf_source_size;
+ return fgl.rect.size * (double)p_size.x / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->fixed_size;
+ return fgl.rect.size * (double)p_size.x / (double)fd->fixed_size;
} else {
- return gl[p_glyph | mod].rect.size * Math::round((double)p_size.x / (double)fd->fixed_size);
+ return fgl.rect.size * Math::round((double)p_size.x / (double)fd->fixed_size);
}
} else {
- return gl[p_glyph | mod].rect.size;
+ return fgl.rect.size;
}
}
@@ -3086,12 +3135,13 @@ void TextServerAdvanced::_font_set_glyph_size(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].rect.size = p_gl_size;
- gl[p_glyph].found = true;
+ fgl.rect.size = p_gl_size;
+ fgl.found = true;
}
Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -3101,22 +3151,23 @@ Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const V
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Rect2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Rect2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Rect2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- return gl[p_glyph | mod].uv_rect;
+ return fgl.uv_rect;
}
void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) {
@@ -3126,12 +3177,13 @@ void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].uv_rect = p_uv_rect;
- gl[p_glyph].found = true;
+ fgl.uv_rect = p_uv_rect;
+ fgl.found = true;
}
int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -3141,22 +3193,23 @@ int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, c
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), -1);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), -1);
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return -1; // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- return gl[p_glyph | mod].texture_idx;
+ return fgl.texture_idx;
}
void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) {
@@ -3166,12 +3219,13 @@ void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, cons
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].texture_idx = p_texture_idx;
- gl[p_glyph].found = true;
+ fgl.texture_idx = p_texture_idx;
+ fgl.found = true;
}
RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -3181,27 +3235,28 @@ RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), RID());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), RID());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return RID(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), RID());
+ ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), RID());
if (RenderingServer::get_singleton() != nullptr) {
- if (gl[p_glyph | mod].texture_idx != -1) {
- if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx];
+ if (fgl.texture_idx != -1) {
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -3214,7 +3269,7 @@ RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const
}
tex.dirty = false;
}
- return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_rid();
+ return ffsd->textures[fgl.texture_idx].texture->get_rid();
}
}
@@ -3228,27 +3283,28 @@ Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, co
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Size2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Size2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Size2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), Size2());
+ ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), Size2());
if (RenderingServer::get_singleton() != nullptr) {
- if (gl[p_glyph | mod].texture_idx != -1) {
- if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx];
+ if (fgl.texture_idx != -1) {
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -3261,7 +3317,7 @@ Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, co
}
tex.dirty = false;
}
- return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_size();
+ return ffsd->textures[fgl.texture_idx].texture->get_size();
}
}
@@ -3275,7 +3331,8 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
#ifdef MODULE_FREETYPE_ENABLED
PackedVector3Array points;
@@ -3283,20 +3340,20 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
- int error = FT_Load_Glyph(fd->cache[size]->face, index, FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0));
+ int error = FT_Load_Glyph(ffsd->face, index, FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0));
ERR_FAIL_COND_V(error, Dictionary());
if (fd->embolden != 0.f) {
FT_Pos strength = fd->embolden * p_size * 4; // 26.6 fractional units (1 / 64).
- FT_Outline_Embolden(&fd->cache[size]->face->glyph->outline, strength);
+ FT_Outline_Embolden(&ffsd->face->glyph->outline, strength);
}
if (fd->transform != Transform2D()) {
FT_Matrix mat = { FT_Fixed(fd->transform[0][0] * 65536), FT_Fixed(fd->transform[0][1] * 65536), FT_Fixed(fd->transform[1][0] * 65536), FT_Fixed(fd->transform[1][1] * 65536) }; // 16.16 fractional units (1 / 65536).
- FT_Outline_Transform(&fd->cache[size]->face->glyph->outline, &mat);
+ FT_Outline_Transform(&ffsd->face->glyph->outline, &mat);
}
- double scale = (1.0 / 64.0) / fd->cache[size]->oversampling * fd->cache[size]->scale;
+ double scale = (1.0 / 64.0) / ffsd->oversampling * ffsd->scale;
if (fd->msdf) {
scale = scale * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
@@ -3306,13 +3363,13 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i
scale = scale * Math::round((double)p_size / (double)fd->fixed_size);
}
}
- for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_points; i++) {
- points.push_back(Vector3(fd->cache[size]->face->glyph->outline.points[i].x * scale, -fd->cache[size]->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fd->cache[size]->face->glyph->outline.tags[i])));
+ for (short i = 0; i < ffsd->face->glyph->outline.n_points; i++) {
+ points.push_back(Vector3(ffsd->face->glyph->outline.points[i].x * scale, -ffsd->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(ffsd->face->glyph->outline.tags[i])));
}
- for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_contours; i++) {
- contours.push_back(fd->cache[size]->face->glyph->outline.contours[i]);
+ for (short i = 0; i < ffsd->face->glyph->outline.n_contours; i++) {
+ contours.push_back(ffsd->face->glyph->outline.contours[i]);
}
- bool orientation = (FT_Outline_Get_Orientation(&fd->cache[size]->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT);
+ bool orientation = (FT_Outline_Get_Orientation(&ffsd->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT);
Dictionary out;
out["points"] = points;
@@ -3331,7 +3388,8 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_fon
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), TypedArray<Vector2i>());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), TypedArray<Vector2i>());
TypedArray<Vector2i> ret;
for (const KeyValue<Vector2i, Vector2> &E : fd->cache[size]->kerning_map) {
@@ -3347,8 +3405,9 @@ void TextServerAdvanced::_font_clear_kerning_map(const RID &p_font_rid, int64_t
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map.clear();
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map.clear();
}
void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) {
@@ -3358,8 +3417,9 @@ void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_s
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map.erase(p_glyph_pair);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map.erase(p_glyph_pair);
}
void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) {
@@ -3369,8 +3429,9 @@ void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map[p_glyph_pair] = p_kerning;
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map[p_glyph_pair] = p_kerning;
}
Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const {
@@ -3380,9 +3441,10 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
- const HashMap<Vector2i, Vector2> &kern = fd->cache[size]->kerning_map;
+ const HashMap<Vector2i, Vector2> &kern = ffsd->kerning_map;
if (kern.has(p_glyph_pair)) {
if (fd->msdf) {
@@ -3398,9 +3460,9 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s
}
} else {
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
FT_Vector delta;
- FT_Get_Kerning(fd->cache[size]->face, p_glyph_pair.x, p_glyph_pair.y, FT_KERNING_DEFAULT, &delta);
+ FT_Get_Kerning(ffsd->face, p_glyph_pair.x, p_glyph_pair.y, FT_KERNING_DEFAULT, &delta);
if (fd->msdf) {
return Vector2(delta.x, delta.y) * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
@@ -3426,14 +3488,15 @@ int64_t TextServerAdvanced::_font_get_glyph_index(const RID &p_font_rid, int64_t
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
if (p_variation_selector) {
- return FT_Face_GetCharVariantIndex(fd->cache[size]->face, p_char, p_variation_selector);
+ return FT_Face_GetCharVariantIndex(ffsd->face, p_char, p_variation_selector);
} else {
- return FT_Get_Char_Index(fd->cache[size]->face, p_char);
+ return FT_Get_Char_Index(ffsd->face, p_char);
}
} else {
return (int64_t)p_char;
@@ -3449,23 +3512,24 @@ int64_t TextServerAdvanced::_font_get_char_from_glyph_index(const RID &p_font_ri
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->inv_glyph_map.is_empty()) {
- FT_Face face = fd->cache[size]->face;
+ if (ffsd->inv_glyph_map.is_empty()) {
+ FT_Face face = ffsd->face;
FT_UInt gindex;
FT_ULong charcode = FT_Get_First_Char(face, &gindex);
while (gindex != 0) {
if (charcode != 0) {
- fd->cache[size]->inv_glyph_map[gindex] = charcode;
+ ffsd->inv_glyph_map[gindex] = charcode;
}
charcode = FT_Get_Next_Char(face, charcode, &gindex);
}
}
- if (fd->cache[size]->inv_glyph_map.has(p_glyph_index)) {
- return fd->cache[size]->inv_glyph_map[p_glyph_index];
+ if (ffsd->inv_glyph_map.has(p_glyph_index)) {
+ return ffsd->inv_glyph_map[p_glyph_index];
} else {
return 0;
}
@@ -3482,17 +3546,19 @@ bool TextServerAdvanced::_font_has_char(const RID &p_font_rid, int64_t p_char) c
}
MutexLock lock(fd->mutex);
+ FontForSizeAdvanced *ffsd = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), false);
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), false);
+ } else {
+ ffsd = fd->cache.begin()->value;
}
- FontForSizeAdvanced *at_size = fd->cache.begin()->value;
#ifdef MODULE_FREETYPE_ENABLED
- if (at_size && at_size->face) {
- return FT_Get_Char_Index(at_size->face, p_char) != 0;
+ if (ffsd->face) {
+ return FT_Get_Char_Index(ffsd->face, p_char) != 0;
}
#endif
- return (at_size) ? at_size->glyph_map.has((int32_t)p_char) : false;
+ return ffsd->glyph_map.has((int32_t)p_char);
}
String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) const {
@@ -3500,30 +3566,30 @@ String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) cons
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
+ FontForSizeAdvanced *ffsd = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), String());
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), String());
+ } else {
+ ffsd = fd->cache.begin()->value;
}
- FontForSizeAdvanced *at_size = fd->cache.begin()->value;
String chars;
#ifdef MODULE_FREETYPE_ENABLED
- if (at_size && at_size->face) {
+ if (ffsd->face) {
FT_UInt gindex;
- FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex);
+ FT_ULong charcode = FT_Get_First_Char(ffsd->face, &gindex);
while (gindex != 0) {
if (charcode != 0) {
chars = chars + String::chr(charcode);
}
- charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex);
+ charcode = FT_Get_Next_Char(ffsd->face, charcode, &gindex);
}
return chars;
}
#endif
- if (at_size) {
- const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map;
- for (const KeyValue<int32_t, FontGlyph> &E : gl) {
- chars = chars + String::chr(E.key);
- }
+ const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map;
+ for (const KeyValue<int32_t, FontGlyph> &E : gl) {
+ chars = chars + String::chr(E.key);
}
return chars;
}
@@ -3533,10 +3599,12 @@ PackedInt32Array TextServerAdvanced::_font_get_supported_glyphs(const RID &p_fon
ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
+ FontForSizeAdvanced *at_size = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), PackedInt32Array());
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), at_size), PackedInt32Array());
+ } else {
+ at_size = fd->cache.begin()->value;
}
- FontForSizeAdvanced *at_size = fd->cache.begin()->value;
PackedInt32Array glyphs;
#ifdef MODULE_FREETYPE_ENABLED
@@ -3567,25 +3635,27 @@ void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
for (int64_t i = p_start; i <= p_end; i++) {
#ifdef MODULE_FREETYPE_ENABLED
- int32_t idx = FT_Get_Char_Index(fd->cache[size]->face, i);
- if (fd->cache[size]->face) {
+ int32_t idx = FT_Get_Char_Index(ffsd->face, i);
+ if (ffsd->face) {
+ FontGlyph fgl;
if (fd->msdf) {
- _ensure_glyph(fd, size, (int32_t)idx);
+ _ensure_glyph(fd, size, (int32_t)idx, fgl);
} else {
for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) {
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl);
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
} else {
- _ensure_glyph(fd, size, (int32_t)idx | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl);
}
}
}
@@ -3600,24 +3670,26 @@ void TextServerAdvanced::_font_render_glyph(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
#ifdef MODULE_FREETYPE_ENABLED
int32_t idx = p_index & 0xffffff; // Remove subpixel shifts.
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
+ FontGlyph fgl;
if (fd->msdf) {
- _ensure_glyph(fd, size, (int32_t)idx);
+ _ensure_glyph(fd, size, (int32_t)idx, fgl);
} else {
for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) {
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl);
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
} else {
- _ensure_glyph(fd, size, (int32_t)idx | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl);
}
}
}
@@ -3634,16 +3706,17 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
bool lcd_aa = false;
#ifdef MODULE_FREETYPE_ENABLED
- if (!fd->msdf && fd->cache[size]->face) {
+ if (!fd->msdf && ffsd->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
@@ -3660,24 +3733,24 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
#endif
- if (!_ensure_glyph(fd, size, index)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, index, fgl)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
- const FontGlyph &gl = fd->cache[size]->glyph_map[index];
- if (gl.found) {
- ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
+ if (fgl.found) {
+ ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size());
- if (gl.texture_idx != -1) {
+ if (fgl.texture_idx != -1) {
Color modulate = p_color;
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
+ if (ffsd->face && ffsd->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
modulate.r = modulate.g = modulate.b = 1.0;
}
#endif
if (RenderingServer::get_singleton() != nullptr) {
- if (fd->cache[size]->textures[gl.texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx];
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -3690,12 +3763,12 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
tex.dirty = false;
}
- RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid();
+ RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid();
if (fd->msdf) {
Point2 cpos = p_pos;
- cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size;
- Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size;
- RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
+ cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size;
+ Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size;
+ RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
} else {
double scale = _font_get_scale(p_font_rid, p_size);
Point2 cpos = p_pos;
@@ -3708,8 +3781,8 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
- Vector2 gpos = gl.rect.position;
- Size2 csize = gl.rect.size;
+ Vector2 gpos = fgl.rect.position;
+ Size2 csize = fgl.rect.size;
if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
double gl_scale = (double)p_size / (double)fd->fixed_size;
@@ -3723,9 +3796,9 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
cpos += gpos;
if (lcd_aa) {
- RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate);
+ RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate);
} else {
- RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
+ RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false);
}
}
}
@@ -3742,16 +3815,17 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size));
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
bool lcd_aa = false;
#ifdef MODULE_FREETYPE_ENABLED
- if (!fd->msdf && fd->cache[size]->face) {
+ if (!fd->msdf && ffsd->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
@@ -3768,24 +3842,24 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
#endif
- if (!_ensure_glyph(fd, size, index)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, index, fgl)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
- const FontGlyph &gl = fd->cache[size]->glyph_map[index];
- if (gl.found) {
- ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
+ if (fgl.found) {
+ ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size());
- if (gl.texture_idx != -1) {
+ if (fgl.texture_idx != -1) {
Color modulate = p_color;
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
+ if (ffsd->face && fd->cache[size]->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
modulate.r = modulate.g = modulate.b = 1.0;
}
#endif
if (RenderingServer::get_singleton() != nullptr) {
- if (fd->cache[size]->textures[gl.texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx];
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -3798,12 +3872,12 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
tex.dirty = false;
}
- RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid();
+ RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid();
if (fd->msdf) {
Point2 cpos = p_pos;
- cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size;
- Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size;
- RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
+ cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size;
+ Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size;
+ RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
} else {
Point2 cpos = p_pos;
double scale = _font_get_scale(p_font_rid, p_size);
@@ -3816,8 +3890,8 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
- Vector2 gpos = gl.rect.position;
- Size2 csize = gl.rect.size;
+ Vector2 gpos = fgl.rect.position;
+ Size2 csize = fgl.rect.size;
if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
double gl_scale = (double)p_size / (double)fd->fixed_size;
@@ -3831,9 +3905,9 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
cpos += gpos;
if (lcd_aa) {
- RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate);
+ RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate);
} else {
- RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
+ RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false);
}
}
}
@@ -3898,7 +3972,8 @@ bool TextServerAdvanced::_font_is_script_supported(const RID &p_font_rid, const
return fd->script_support_overrides[p_script];
} else {
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), false);
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), false);
return fd->supported_scripts.has(hb_tag_from_string(p_script.ascii().get_data(), -1));
}
}
@@ -3945,7 +4020,8 @@ void TextServerAdvanced::_font_set_opentype_feature_overrides(const RID &p_font_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->feature_overrides = p_overrides;
}
@@ -3963,7 +4039,8 @@ Dictionary TextServerAdvanced::_font_supported_feature_list(const RID &p_font_ri
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
return fd->supported_features;
}
@@ -3973,7 +4050,8 @@ Dictionary TextServerAdvanced::_font_supported_variation_list(const RID &p_font_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeAdvanced *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
return fd->supported_varaitions;
}
@@ -4048,7 +4126,7 @@ int64_t TextServerAdvanced::_convert_pos_inv(const ShapedTextDataAdvanced *p_sd,
}
void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *p_shaped, bool p_text) {
- p_shaped->valid = false;
+ p_shaped->valid.clear();
p_shaped->sort_valid = false;
p_shaped->line_breaks_valid = false;
p_shaped->justification_ops_valid = false;
@@ -4404,7 +4482,7 @@ bool TextServerAdvanced::_shaped_text_resize_object(const RID &p_shaped, const V
sd->objects[p_key].rect.size = p_size;
sd->objects[p_key].inline_align = p_inline_align;
sd->objects[p_key].baseline = p_baseline;
- if (sd->valid) {
+ if (sd->valid.is_set()) {
// Recalc string metrics.
sd->ascent = 0;
sd->descent = 0;
@@ -4548,7 +4626,7 @@ RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start
if (sd->parent != RID()) {
return _shaped_text_substr(sd->parent, p_start, p_length);
}
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
ERR_FAIL_COND_V(p_start < 0 || p_length < 0, RID());
@@ -4576,7 +4654,7 @@ RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start
}
bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_length) const {
- if (p_new_sd->valid) {
+ if (p_new_sd->valid.is_set()) {
return true;
}
@@ -4725,7 +4803,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
_realign(p_new_sd);
}
- p_new_sd->valid = true;
+ p_new_sd->valid.set();
return true;
}
@@ -4743,7 +4821,7 @@ double TextServerAdvanced::_shaped_text_fit_to_width(const RID &p_shaped, double
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
if (!sd->justification_ops_valid) {
@@ -4900,7 +4978,7 @@ double TextServerAdvanced::_shaped_text_tab_align(const RID &p_shaped, const Pac
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
if (!sd->line_breaks_valid) {
@@ -5117,7 +5195,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
ERR_FAIL_NULL_MSG(sd, "ShapedTextDataAdvanced invalid.");
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped_line);
}
@@ -5144,7 +5222,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
Vector<ShapedTextDataAdvanced::Span> &spans = sd->spans;
if (sd->parent != RID()) {
ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent);
- ERR_FAIL_COND(!parent_sd->valid);
+ ERR_FAIL_COND(!parent_sd->valid.is_set());
spans = parent_sd->spans;
}
@@ -5355,7 +5433,7 @@ void TextServerAdvanced::_update_chars(ShapedTextDataAdvanced *p_sd) const {
Vector<ShapedTextDataAdvanced::Span> &spans = p_sd->spans;
if (p_sd->parent != RID()) {
ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(p_sd->parent);
- ERR_FAIL_COND(!parent_sd->valid);
+ ERR_FAIL_COND(!parent_sd->valid.is_set());
spans = parent_sd->spans;
}
@@ -5403,7 +5481,7 @@ PackedInt32Array TextServerAdvanced::_shaped_text_get_character_breaks(const RID
ERR_FAIL_NULL_V(sd, PackedInt32Array());
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
@@ -5417,7 +5495,7 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) {
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped);
}
@@ -5688,7 +5766,7 @@ bool TextServerAdvanced::_shaped_text_update_justification_ops(const RID &p_shap
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped);
}
if (!sd->line_breaks_valid) {
@@ -6068,7 +6146,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
@@ -6128,7 +6206,8 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
gl.index = glyph_info[i].codepoint;
if (gl.index != 0) {
- _ensure_glyph(fd, fss, gl.index | mod);
+ FontGlyph fgl;
+ _ensure_glyph(fd, fss, gl.index | mod, fgl);
if (subpos) {
gl.x_off = (double)glyph_pos[i].x_offset / (64.0 / scale);
} else if (p_sd->orientation == ORIENTATION_HORIZONTAL) {
@@ -6240,7 +6319,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (sd->valid) {
+ if (sd->valid.is_set()) {
return true;
}
@@ -6248,13 +6327,13 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
if (sd->parent != RID()) {
_shaped_text_shape(sd->parent);
ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent);
- ERR_FAIL_COND_V(!parent_sd->valid, false);
+ ERR_FAIL_COND_V(!parent_sd->valid.is_set(), false);
ERR_FAIL_COND_V(!_shape_substr(sd, parent_sd, sd->start, sd->end - sd->start), false);
return true;
}
if (sd->text.length() == 0) {
- sd->valid = true;
+ sd->valid.set();
return true;
}
@@ -6447,16 +6526,16 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
}
_realign(sd);
- sd->valid = true;
- return sd->valid;
+ sd->valid.set();
+ return sd->valid.is_set();
}
bool TextServerAdvanced::_shaped_text_is_ready(const RID &p_shaped) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL_V(sd, false);
- MutexLock lock(sd->mutex);
- return sd->valid;
+ // Atomic read is safe and faster.
+ return sd->valid.is_set();
}
const Glyph *TextServerAdvanced::_shaped_text_get_glyphs(const RID &p_shaped) const {
@@ -6464,7 +6543,7 @@ const Glyph *TextServerAdvanced::_shaped_text_get_glyphs(const RID &p_shaped) co
ERR_FAIL_NULL_V(sd, nullptr);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return sd->glyphs.ptr();
@@ -6475,7 +6554,7 @@ int64_t TextServerAdvanced::_shaped_text_get_glyph_count(const RID &p_shaped) co
ERR_FAIL_NULL_V(sd, 0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return sd->glyphs.size();
@@ -6486,7 +6565,7 @@ const Glyph *TextServerAdvanced::_shaped_text_sort_logical(const RID &p_shaped)
ERR_FAIL_NULL_V(sd, nullptr);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
@@ -6526,7 +6605,7 @@ Rect2 TextServerAdvanced::_shaped_text_get_object_rect(const RID &p_shaped, cons
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2());
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return sd->objects[p_key].rect;
@@ -6547,7 +6626,7 @@ int64_t TextServerAdvanced::_shaped_text_get_object_glyph(const RID &p_shaped, c
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(!sd->objects.has(p_key), -1);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
const ShapedTextDataAdvanced::EmbeddedObject &obj = sd->objects[p_key];
@@ -6566,7 +6645,7 @@ Size2 TextServerAdvanced::_shaped_text_get_size(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, Size2());
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) {
@@ -6581,7 +6660,7 @@ double TextServerAdvanced::_shaped_text_get_ascent(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return sd->ascent + sd->extra_spacing[SPACING_TOP];
@@ -6592,7 +6671,7 @@ double TextServerAdvanced::_shaped_text_get_descent(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return sd->descent + sd->extra_spacing[SPACING_BOTTOM];
@@ -6603,7 +6682,7 @@ double TextServerAdvanced::_shaped_text_get_width(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
return Math::ceil(sd->text_trimmed ? sd->width_trimmed : sd->width);
@@ -6614,7 +6693,7 @@ double TextServerAdvanced::_shaped_text_get_underline_position(const RID &p_shap
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
@@ -6626,7 +6705,7 @@ double TextServerAdvanced::_shaped_text_get_underline_thickness(const RID &p_sha
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
}
@@ -7424,10 +7503,15 @@ bool TextServerAdvanced::_is_valid_letter(uint64_t p_unicode) const {
return u_isalpha(p_unicode);
}
+void TextServerAdvanced::_update_settings() {
+ lcd_subpixel_layout.set((TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"));
+}
+
TextServerAdvanced::TextServerAdvanced() {
_insert_num_systems_lang();
_insert_feature_sets();
_bmp_create_font_funcs();
+ ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextServerAdvanced::_update_settings));
}
void TextServerAdvanced::_cleanup() {
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index fdebb8e4cd..448be9ebe4 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -74,6 +74,7 @@
#include <godot_cpp/templates/hash_map.hpp>
#include <godot_cpp/templates/hash_set.hpp>
#include <godot_cpp/templates/rid_owner.hpp>
+#include <godot_cpp/templates/safe_refcount.hpp>
#include <godot_cpp/templates/vector.hpp>
using namespace godot;
@@ -85,6 +86,7 @@ using namespace godot;
#include "core/object/worker_thread_pool.h"
#include "core/templates/hash_map.h"
#include "core/templates/rid_owner.h"
+#include "core/templates/safe_refcount.h"
#include "scene/resources/image_texture.h"
#include "servers/text/text_server_extension.h"
@@ -151,6 +153,9 @@ class TextServerAdvanced : public TextServerExtension {
HashMap<StringName, int32_t> feature_sets;
HashMap<int32_t, FeatureInfo> feature_sets_inv;
+ SafeNumeric<TextServer::FontLCDSubpixelLayout> lcd_subpixel_layout{ TextServer::FontLCDSubpixelLayout::FONT_LCD_SUBPIXEL_LAYOUT_NONE };
+ void _update_settings();
+
void _insert_num_systems_lang();
void _insert_feature_sets();
_FORCE_INLINE_ void _insert_feature(const StringName &p_name, int32_t p_tag, Variant::Type p_vtype = Variant::INT, bool p_hidden = false);
@@ -327,7 +332,7 @@ class TextServerAdvanced : public TextServerExtension {
int extra_spacing[4] = { 0, 0, 0, 0 };
double baseline_offset = 0.0;
- HashMap<Vector2i, FontForSizeAdvanced *, VariantHasher, VariantComparator> cache;
+ HashMap<Vector2i, FontForSizeAdvanced *> cache;
bool face_init = false;
HashSet<uint32_t> supported_scripts;
@@ -359,8 +364,8 @@ class TextServerAdvanced : public TextServerExtension {
#ifdef MODULE_FREETYPE_ENABLED
_FORCE_INLINE_ FontGlyph rasterize_bitmap(FontForSizeAdvanced *p_data, int p_rect_margin, FT_Bitmap p_bitmap, int p_yofs, int p_xofs, const Vector2 &p_advance, bool p_bgra) const;
#endif
- _FORCE_INLINE_ bool _ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph) const;
- _FORCE_INLINE_ bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size) const;
+ _FORCE_INLINE_ bool _ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const;
+ _FORCE_INLINE_ bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size) const;
_FORCE_INLINE_ void _font_clear_cache(FontAdvanced *p_font_data);
static void _generateMTSDF_threaded(void *p_td, uint32_t p_y);
@@ -487,7 +492,7 @@ class TextServerAdvanced : public TextServerExtension {
/* Shaped data */
TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction.
int base_para_direction = UBIDI_DEFAULT_LTR;
- bool valid = false; // String is shaped.
+ SafeFlag valid{ false }; // String is shaped.
bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted).
bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string.
bool sort_valid = false;
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index a7ddfc719e..540ba19cac 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -624,18 +624,21 @@ _FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_bitma
/* Font Cache */
/*************************************************************************/
-_FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false);
+_FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const {
+ FontForSizeFallback *fd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size, fd), false);
int32_t glyph_index = p_glyph & 0xffffff; // Remove subpixel shifts.
- FontForSizeFallback *fd = p_font_data->cache[p_size];
- if (fd->glyph_map.has(p_glyph)) {
- return fd->glyph_map[p_glyph].found;
+ HashMap<int32_t, FontGlyph>::Iterator E = fd->glyph_map.find(p_glyph);
+ if (E) {
+ r_glyph = E->value;
+ return E->value.found;
}
if (glyph_index == 0) { // Non graphical or invalid glyph, do not render.
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return true;
}
@@ -673,7 +676,8 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data,
int error = FT_Load_Glyph(fd->face, glyph_index, flags);
if (error) {
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return false;
}
@@ -777,20 +781,26 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data,
cleanup_stroker:
FT_Stroker_Done(stroker);
}
- fd->glyph_map[p_glyph] = gl;
+ E = fd->glyph_map.insert(p_glyph, gl);
+ r_glyph = E->value;
return gl.found;
}
#endif
- fd->glyph_map[p_glyph] = FontGlyph();
+ E = fd->glyph_map.insert(p_glyph, FontGlyph());
+ r_glyph = E->value;
return false;
}
-_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size) const {
+_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size) const {
ERR_FAIL_COND_V(p_size.x <= 0, false);
- if (p_font_data->cache.has(p_size)) {
+
+ HashMap<Vector2i, FontForSizeFallback *>::Iterator E = p_font_data->cache.find(p_size);
+ if (E) {
+ r_cache_for_size = E->value;
return true;
}
+ r_cache_for_size = nullptr;
FontForSizeFallback *fd = memnew(FontForSizeFallback);
fd->size = p_size;
if (p_font_data->data_ptr && (p_font_data->data_size > 0)) {
@@ -973,7 +983,9 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f
ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!");
#endif
}
- p_font_data->cache[p_size] = fd;
+
+ p_font_data->cache.insert(p_size, fd);
+ r_cache_for_size = fd;
return true;
}
@@ -1041,7 +1053,8 @@ void TextServerFallback::_font_set_style(const RID &p_font_rid, BitField<FontSty
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->style_flags = p_style;
}
@@ -1119,7 +1132,8 @@ BitField<TextServer::FontStyle> TextServerFallback::_font_get_style(const RID &p
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
return fd->style_flags;
}
@@ -1129,7 +1143,8 @@ void TextServerFallback::_font_set_style_name(const RID &p_font_rid, const Strin
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->style_name = p_name;
}
@@ -1139,7 +1154,8 @@ String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String());
return fd->style_name;
}
@@ -1149,7 +1165,8 @@ void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weigh
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->weight = CLAMP(p_weight, 100, 999);
}
@@ -1159,7 +1176,8 @@ int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 400);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 400);
return fd->weight;
}
@@ -1169,7 +1187,8 @@ void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stre
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->stretch = CLAMP(p_stretch, 50, 200);
}
@@ -1179,7 +1198,8 @@ int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 100);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 100);
return fd->stretch;
}
@@ -1189,7 +1209,8 @@ void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_n
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->font_name = p_name;
}
@@ -1199,7 +1220,8 @@ String TextServerFallback::_font_get_name(const RID &p_font_rid) const {
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String());
return fd->font_name;
}
@@ -1607,8 +1629,9 @@ void TextServerFallback::_font_set_ascent(const RID &p_font_rid, int64_t p_size,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->ascent = p_ascent;
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->ascent = p_ascent;
}
double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const {
@@ -1618,18 +1641,19 @@ double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_siz
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->ascent * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->ascent * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->ascent * (double)p_size / (double)fd->fixed_size;
+ return ffsd->ascent * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->ascent * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->ascent * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->ascent;
+ return ffsd->ascent;
}
}
@@ -1639,8 +1663,9 @@ void TextServerFallback::_font_set_descent(const RID &p_font_rid, int64_t p_size
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->descent = p_descent;
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->descent = p_descent;
}
double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_size) const {
@@ -1650,18 +1675,19 @@ double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_si
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->descent * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->descent * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->descent * (double)p_size / (double)fd->fixed_size;
+ return ffsd->descent * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->descent * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->descent * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->descent;
+ return ffsd->descent;
}
}
@@ -1672,8 +1698,9 @@ void TextServerFallback::_font_set_underline_position(const RID &p_font_rid, int
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->underline_position = p_underline_position;
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->underline_position = p_underline_position;
}
double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const {
@@ -1683,18 +1710,19 @@ double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, i
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->underline_position * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->underline_position * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->underline_position * (double)p_size / (double)fd->fixed_size;
+ return ffsd->underline_position * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->underline_position * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->underline_position * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->underline_position;
+ return ffsd->underline_position;
}
}
@@ -1705,8 +1733,9 @@ void TextServerFallback::_font_set_underline_thickness(const RID &p_font_rid, in
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->underline_thickness = p_underline_thickness;
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->underline_thickness = p_underline_thickness;
}
double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const {
@@ -1716,18 +1745,19 @@ double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->underline_thickness * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->fixed_size;
+ return ffsd->underline_thickness * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->underline_thickness;
+ return ffsd->underline_thickness;
}
}
@@ -1738,13 +1768,14 @@ void TextServerFallback::_font_set_scale(const RID &p_font_rid, int64_t p_size,
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
return; // Do not override scale for dynamic fonts, it's calculated automatically.
}
#endif
- fd->cache[size]->scale = p_scale;
+ ffsd->scale = p_scale;
}
double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size) const {
@@ -1754,18 +1785,19 @@ double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0);
if (fd->msdf) {
- return fd->cache[size]->scale * (double)p_size / (double)fd->msdf_source_size;
+ return ffsd->scale * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return fd->cache[size]->scale * (double)p_size / (double)fd->fixed_size;
+ return ffsd->scale * (double)p_size / (double)fd->fixed_size;
} else {
- return fd->cache[size]->scale * Math::round((double)p_size / (double)fd->fixed_size);
+ return ffsd->scale * Math::round((double)p_size / (double)fd->fixed_size);
}
} else {
- return fd->cache[size]->scale / fd->cache[size]->oversampling;
+ return ffsd->scale / ffsd->oversampling;
}
}
@@ -1776,9 +1808,10 @@ int64_t TextServerFallback::_font_get_texture_count(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0);
- return fd->cache[size]->textures.size();
+ return ffsd->textures.size();
}
void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) {
@@ -1787,8 +1820,9 @@ void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->textures.clear();
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->textures.clear();
}
void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) {
@@ -1797,10 +1831,11 @@ void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- ERR_FAIL_INDEX(p_texture_index, fd->cache[size]->textures.size());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ERR_FAIL_INDEX(p_texture_index, ffsd->textures.size());
- fd->cache[size]->textures.remove_at(p_texture_index);
+ ffsd->textures.remove_at(p_texture_index);
}
void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) {
@@ -1810,13 +1845,14 @@ void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
ERR_FAIL_COND(p_texture_index < 0);
- if (p_texture_index >= fd->cache[size]->textures.size()) {
- fd->cache[size]->textures.resize(p_texture_index + 1);
+ if (p_texture_index >= ffsd->textures.size()) {
+ ffsd->textures.resize(p_texture_index + 1);
}
- ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index];
+ ShelfPackTexture &tex = ffsd->textures.write[p_texture_index];
tex.image = p_image;
tex.texture_w = p_image->get_width();
@@ -1837,10 +1873,11 @@ Ref<Image> TextServerFallback::_font_get_texture_image(const RID &p_font_rid, co
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Ref<Image>());
- ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), Ref<Image>());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Ref<Image>());
+ ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), Ref<Image>());
- const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index];
+ const ShelfPackTexture &tex = ffsd->textures[p_texture_index];
return tex.image;
}
@@ -1851,13 +1888,14 @@ void TextServerFallback::_font_set_texture_offsets(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
ERR_FAIL_COND(p_texture_index < 0);
- if (p_texture_index >= fd->cache[size]->textures.size()) {
- fd->cache[size]->textures.resize(p_texture_index + 1);
+ if (p_texture_index >= ffsd->textures.size()) {
+ ffsd->textures.resize(p_texture_index + 1);
}
- ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index];
+ ShelfPackTexture &tex = ffsd->textures.write[p_texture_index];
tex.shelves.clear();
for (int32_t i = 0; i < p_offsets.size(); i += 4) {
tex.shelves.push_back(Shelf(p_offsets[i], p_offsets[i + 1], p_offsets[i + 2], p_offsets[i + 3]));
@@ -1870,10 +1908,11 @@ PackedInt32Array TextServerFallback::_font_get_texture_offsets(const RID &p_font
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array());
- ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), PackedInt32Array());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array());
+ ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), PackedInt32Array());
- const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index];
+ const ShelfPackTexture &tex = ffsd->textures[p_texture_index];
PackedInt32Array ret;
ret.resize(tex.shelves.size() * 4);
@@ -1895,10 +1934,11 @@ PackedInt32Array TextServerFallback::_font_get_glyph_list(const RID &p_font_rid,
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array());
PackedInt32Array ret;
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map;
for (const KeyValue<int32_t, FontGlyph> &E : gl) {
ret.push_back(E.key);
}
@@ -1911,9 +1951,10 @@ void TextServerFallback::_font_clear_glyphs(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- fd->cache[size]->glyph_map.clear();
+ ffsd->glyph_map.clear();
}
void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) {
@@ -1922,9 +1963,10 @@ void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- fd->cache[size]->glyph_map.erase(p_glyph);
+ ffsd->glyph_map.erase(p_glyph);
}
Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph) const {
@@ -1934,22 +1976,22 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
Vector2 ea;
if (fd->embolden != 0.0) {
ea.x = fd->embolden * double(size.x) / 64.0;
@@ -1957,17 +1999,17 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64
double scale = _font_get_scale(p_font_rid, p_size);
if (fd->msdf) {
- return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->msdf_source_size;
+ return (fgl.advance + ea) * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->fixed_size;
+ return (fgl.advance + ea) * (double)p_size / (double)fd->fixed_size;
} else {
- return (gl[p_glyph | mod].advance + ea) * Math::round((double)p_size / (double)fd->fixed_size);
+ return (fgl.advance + ea) * Math::round((double)p_size / (double)fd->fixed_size);
}
} else if ((scale == 1.0) && ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE))) {
- return (gl[p_glyph | mod].advance + ea).round();
+ return (fgl.advance + ea).round();
} else {
- return gl[p_glyph | mod].advance + ea;
+ return fgl.advance + ea;
}
}
@@ -1978,12 +2020,13 @@ void TextServerFallback::_font_set_glyph_advance(const RID &p_font_rid, int64_t
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].advance = p_advance;
- gl[p_glyph].found = true;
+ fgl.advance = p_advance;
+ fgl.found = true;
}
Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -1993,32 +2036,32 @@ Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
if (fd->msdf) {
- return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->msdf_source_size;
+ return fgl.rect.position * (double)p_size.x / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->fixed_size;
+ return fgl.rect.position * (double)p_size.x / (double)fd->fixed_size;
} else {
- return gl[p_glyph | mod].rect.position * Math::round((double)p_size.x / (double)fd->fixed_size);
+ return fgl.rect.position * Math::round((double)p_size.x / (double)fd->fixed_size);
}
} else {
- return gl[p_glyph | mod].rect.position;
+ return fgl.rect.position;
}
}
@@ -2029,12 +2072,13 @@ void TextServerFallback::_font_set_glyph_offset(const RID &p_font_rid, const Vec
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].rect.position = p_offset;
- gl[p_glyph].found = true;
+ fgl.rect.position = p_offset;
+ fgl.found = true;
}
Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -2044,32 +2088,32 @@ Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
-
if (fd->msdf) {
- return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->msdf_source_size;
+ return fgl.rect.size * (double)p_size.x / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
- return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->fixed_size;
+ return fgl.rect.size * (double)p_size.x / (double)fd->fixed_size;
} else {
- return gl[p_glyph | mod].rect.size * Math::round((double)p_size.x / (double)fd->fixed_size);
+ return fgl.rect.size * Math::round((double)p_size.x / (double)fd->fixed_size);
}
} else {
- return gl[p_glyph | mod].rect.size;
+ return fgl.rect.size;
}
}
@@ -2080,12 +2124,13 @@ void TextServerFallback::_font_set_glyph_size(const RID &p_font_rid, const Vecto
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].rect.size = p_gl_size;
- gl[p_glyph].found = true;
+ fgl.rect.size = p_gl_size;
+ fgl.found = true;
}
Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -2095,22 +2140,23 @@ Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const V
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Rect2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Rect2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Rect2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- return gl[p_glyph | mod].uv_rect;
+ return fgl.uv_rect;
}
void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) {
@@ -2120,12 +2166,13 @@ void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].uv_rect = p_uv_rect;
- gl[p_glyph].found = true;
+ fgl.uv_rect = p_uv_rect;
+ fgl.found = true;
}
int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -2135,22 +2182,23 @@ int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, c
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), -1);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), -1);
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return -1; // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- return gl[p_glyph | mod].texture_idx;
+ return fgl.texture_idx;
}
void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) {
@@ -2160,12 +2208,13 @@ void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, cons
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
- HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
+ FontGlyph &fgl = ffsd->glyph_map[p_glyph];
- gl[p_glyph].texture_idx = p_texture_idx;
- gl[p_glyph].found = true;
+ fgl.texture_idx = p_texture_idx;
+ fgl.found = true;
}
RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
@@ -2175,27 +2224,28 @@ RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), RID());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), RID());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return RID(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), RID());
+ ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), RID());
if (RenderingServer::get_singleton() != nullptr) {
- if (gl[p_glyph | mod].texture_idx != -1) {
- if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx];
+ if (fgl.texture_idx != -1) {
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -2208,7 +2258,7 @@ RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const
}
tex.dirty = false;
}
- return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_rid();
+ return ffsd->textures[fgl.texture_idx].texture->get_rid();
}
}
@@ -2222,27 +2272,28 @@ Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, co
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Size2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Size2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
- if (!_ensure_glyph(fd, size, p_glyph | mod)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) {
return Size2(); // Invalid or non graphicl glyph, do not display errors.
}
- const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
- ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), Size2());
+ ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), Size2());
if (RenderingServer::get_singleton() != nullptr) {
- if (gl[p_glyph | mod].texture_idx != -1) {
- if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx];
+ if (fgl.texture_idx != -1) {
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -2255,7 +2306,7 @@ Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, co
}
tex.dirty = false;
}
- return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_size();
+ return ffsd->textures[fgl.texture_idx].texture->get_size();
}
}
@@ -2269,7 +2320,8 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
#ifdef MODULE_FREETYPE_ENABLED
PackedVector3Array points;
@@ -2277,20 +2329,20 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
- int error = FT_Load_Glyph(fd->cache[size]->face, FT_Get_Char_Index(fd->cache[size]->face, index), FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0));
+ int error = FT_Load_Glyph(ffsd->face, FT_Get_Char_Index(ffsd->face, index), FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0));
ERR_FAIL_COND_V(error, Dictionary());
if (fd->embolden != 0.f) {
FT_Pos strength = fd->embolden * p_size * 4; // 26.6 fractional units (1 / 64).
- FT_Outline_Embolden(&fd->cache[size]->face->glyph->outline, strength);
+ FT_Outline_Embolden(&ffsd->face->glyph->outline, strength);
}
if (fd->transform != Transform2D()) {
FT_Matrix mat = { FT_Fixed(fd->transform[0][0] * 65536), FT_Fixed(fd->transform[0][1] * 65536), FT_Fixed(fd->transform[1][0] * 65536), FT_Fixed(fd->transform[1][1] * 65536) }; // 16.16 fractional units (1 / 65536).
- FT_Outline_Transform(&fd->cache[size]->face->glyph->outline, &mat);
+ FT_Outline_Transform(&ffsd->face->glyph->outline, &mat);
}
- double scale = (1.0 / 64.0) / fd->cache[size]->oversampling * fd->cache[size]->scale;
+ double scale = (1.0 / 64.0) / ffsd->oversampling * ffsd->scale;
if (fd->msdf) {
scale = scale * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
@@ -2300,13 +2352,13 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i
scale = scale * Math::round((double)p_size / (double)fd->fixed_size);
}
}
- for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_points; i++) {
- points.push_back(Vector3(fd->cache[size]->face->glyph->outline.points[i].x * scale, -fd->cache[size]->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fd->cache[size]->face->glyph->outline.tags[i])));
+ for (short i = 0; i < ffsd->face->glyph->outline.n_points; i++) {
+ points.push_back(Vector3(ffsd->face->glyph->outline.points[i].x * scale, -ffsd->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(ffsd->face->glyph->outline.tags[i])));
}
- for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_contours; i++) {
- contours.push_back(fd->cache[size]->face->glyph->outline.contours[i]);
+ for (short i = 0; i < ffsd->face->glyph->outline.n_contours; i++) {
+ contours.push_back(ffsd->face->glyph->outline.contours[i]);
}
- bool orientation = (FT_Outline_Get_Orientation(&fd->cache[size]->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT);
+ bool orientation = (FT_Outline_Get_Orientation(&ffsd->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT);
Dictionary out;
out["points"] = points;
@@ -2325,10 +2377,11 @@ TypedArray<Vector2i> TextServerFallback::_font_get_kerning_list(const RID &p_fon
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), TypedArray<Vector2i>());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), TypedArray<Vector2i>());
TypedArray<Vector2i> ret;
- for (const KeyValue<Vector2i, Vector2> &E : fd->cache[size]->kerning_map) {
+ for (const KeyValue<Vector2i, Vector2> &E : ffsd->kerning_map) {
ret.push_back(E.key);
}
return ret;
@@ -2341,8 +2394,9 @@ void TextServerFallback::_font_clear_kerning_map(const RID &p_font_rid, int64_t
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map.clear();
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map.clear();
}
void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) {
@@ -2352,8 +2406,9 @@ void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_s
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map.erase(p_glyph_pair);
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map.erase(p_glyph_pair);
}
void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) {
@@ -2363,8 +2418,9 @@ void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
- fd->cache[size]->kerning_map[p_glyph_pair] = p_kerning;
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
+ ffsd->kerning_map[p_glyph_pair] = p_kerning;
}
Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const {
@@ -2374,9 +2430,10 @@ Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_s
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2());
- const HashMap<Vector2i, Vector2> &kern = fd->cache[size]->kerning_map;
+ const HashMap<Vector2i, Vector2> &kern = ffsd->kerning_map;
if (kern.has(p_glyph_pair)) {
if (fd->msdf) {
@@ -2392,11 +2449,11 @@ Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_s
}
} else {
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
FT_Vector delta;
- int32_t glyph_a = FT_Get_Char_Index(fd->cache[size]->face, p_glyph_pair.x);
- int32_t glyph_b = FT_Get_Char_Index(fd->cache[size]->face, p_glyph_pair.y);
- FT_Get_Kerning(fd->cache[size]->face, glyph_a, glyph_b, FT_KERNING_DEFAULT, &delta);
+ int32_t glyph_a = FT_Get_Char_Index(ffsd->face, p_glyph_pair.x);
+ int32_t glyph_b = FT_Get_Char_Index(ffsd->face, p_glyph_pair.y);
+ FT_Get_Kerning(ffsd->face, glyph_a, glyph_b, FT_KERNING_DEFAULT, &delta);
if (fd->msdf) {
return Vector2(delta.x, delta.y) * (double)p_size / (double)fd->msdf_source_size;
} else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
@@ -2431,17 +2488,19 @@ bool TextServerFallback::_font_has_char(const RID &p_font_rid, int64_t p_char) c
}
MutexLock lock(fd->mutex);
+ FontForSizeFallback *ffsd = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), false);
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), false);
+ } else {
+ ffsd = fd->cache.begin()->value;
}
- FontForSizeFallback *at_size = fd->cache.begin()->value;
#ifdef MODULE_FREETYPE_ENABLED
- if (at_size && at_size->face) {
- return FT_Get_Char_Index(at_size->face, p_char) != 0;
+ if (ffsd->face) {
+ return FT_Get_Char_Index(ffsd->face, p_char) != 0;
}
#endif
- return (at_size) ? at_size->glyph_map.has((int32_t)p_char) : false;
+ return ffsd->glyph_map.has((int32_t)p_char);
}
String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) const {
@@ -2449,30 +2508,30 @@ String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) cons
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
+ FontForSizeFallback *ffsd = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), String());
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), String());
+ } else {
+ ffsd = fd->cache.begin()->value;
}
- FontForSizeFallback *at_size = fd->cache.begin()->value;
String chars;
#ifdef MODULE_FREETYPE_ENABLED
- if (at_size && at_size->face) {
+ if (ffsd->face) {
FT_UInt gindex;
- FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex);
+ FT_ULong charcode = FT_Get_First_Char(ffsd->face, &gindex);
while (gindex != 0) {
if (charcode != 0) {
chars = chars + String::chr(charcode);
}
- charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex);
+ charcode = FT_Get_Next_Char(ffsd->face, charcode, &gindex);
}
return chars;
}
#endif
- if (at_size) {
- const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map;
- for (const KeyValue<int32_t, FontGlyph> &E : gl) {
- chars = chars + String::chr(E.key);
- }
+ const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map;
+ for (const KeyValue<int32_t, FontGlyph> &E : gl) {
+ chars = chars + String::chr(E.key);
}
return chars;
}
@@ -2482,10 +2541,12 @@ PackedInt32Array TextServerFallback::_font_get_supported_glyphs(const RID &p_fon
ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
+ FontForSizeFallback *at_size = nullptr;
if (fd->cache.is_empty()) {
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), PackedInt32Array());
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), at_size), PackedInt32Array());
+ } else {
+ at_size = fd->cache.begin()->value;
}
- FontForSizeFallback *at_size = fd->cache.begin()->value;
PackedInt32Array glyphs;
#ifdef MODULE_FREETYPE_ENABLED
@@ -2516,25 +2577,27 @@ void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
for (int64_t i = p_start; i <= p_end; i++) {
#ifdef MODULE_FREETYPE_ENABLED
int32_t idx = i;
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
+ FontGlyph fgl;
if (fd->msdf) {
- _ensure_glyph(fd, size, (int32_t)idx);
+ _ensure_glyph(fd, size, (int32_t)idx, fgl);
} else {
for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) {
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl);
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
} else {
- _ensure_glyph(fd, size, (int32_t)idx | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl);
}
}
}
@@ -2549,24 +2612,26 @@ void TextServerFallback::_font_render_glyph(const RID &p_font_rid, const Vector2
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
#ifdef MODULE_FREETYPE_ENABLED
int32_t idx = p_index & 0xffffff; // Remove subpixel shifts.
- if (fd->cache[size]->face) {
+ if (ffsd->face) {
+ FontGlyph fgl;
if (fd->msdf) {
- _ensure_glyph(fd, size, (int32_t)idx);
+ _ensure_glyph(fd, size, (int32_t)idx, fgl);
} else {
for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) {
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl);
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
- _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
- _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl);
+ _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl);
} else {
- _ensure_glyph(fd, size, (int32_t)idx | (aa << 24));
+ _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl);
}
}
}
@@ -2583,16 +2648,17 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
bool lcd_aa = false;
#ifdef MODULE_FREETYPE_ENABLED
- if (!fd->msdf && fd->cache[size]->face) {
+ if (!fd->msdf && ffsd->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
@@ -2609,24 +2675,24 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
#endif
- if (!_ensure_glyph(fd, size, index)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, index, fgl)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
- const FontGlyph &gl = fd->cache[size]->glyph_map[index];
- if (gl.found) {
- ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
+ if (fgl.found) {
+ ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size());
- if (gl.texture_idx != -1) {
+ if (fgl.texture_idx != -1) {
Color modulate = p_color;
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
+ if (ffsd->face && ffsd->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
modulate.r = modulate.g = modulate.b = 1.0;
}
#endif
if (RenderingServer::get_singleton() != nullptr) {
- if (fd->cache[size]->textures[gl.texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx];
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -2639,12 +2705,12 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
tex.dirty = false;
}
- RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid();
+ RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid();
if (fd->msdf) {
Point2 cpos = p_pos;
- cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size;
- Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size;
- RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
+ cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size;
+ Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size;
+ RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
} else {
Point2 cpos = p_pos;
double scale = _font_get_scale(p_font_rid, p_size);
@@ -2657,8 +2723,8 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
- Vector2 gpos = gl.rect.position;
- Size2 csize = gl.rect.size;
+ Vector2 gpos = fgl.rect.position;
+ Size2 csize = fgl.rect.size;
if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
double gl_scale = (double)p_size / (double)fd->fixed_size;
@@ -2672,9 +2738,9 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
cpos += gpos;
if (lcd_aa) {
- RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate);
+ RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate);
} else {
- RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
+ RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false);
}
}
}
@@ -2691,16 +2757,17 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size));
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
bool lcd_aa = false;
#ifdef MODULE_FREETYPE_ENABLED
- if (!fd->msdf && fd->cache[size]->face) {
+ if (!fd->msdf && ffsd->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
- TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
+ TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get();
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
@@ -2717,24 +2784,24 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
#endif
- if (!_ensure_glyph(fd, size, index)) {
+ FontGlyph fgl;
+ if (!_ensure_glyph(fd, size, index, fgl)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
- const FontGlyph &gl = fd->cache[size]->glyph_map[index];
- if (gl.found) {
- ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
+ if (fgl.found) {
+ ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size());
- if (gl.texture_idx != -1) {
+ if (fgl.texture_idx != -1) {
Color modulate = p_color;
#ifdef MODULE_FREETYPE_ENABLED
- if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
+ if (ffsd->face && ffsd->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
modulate.r = modulate.g = modulate.b = 1.0;
}
#endif
if (RenderingServer::get_singleton() != nullptr) {
- if (fd->cache[size]->textures[gl.texture_idx].dirty) {
- ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx];
+ if (ffsd->textures[fgl.texture_idx].dirty) {
+ ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx];
Ref<Image> img = tex.image;
if (fd->mipmaps && !img->has_mipmaps()) {
img = tex.image->duplicate();
@@ -2747,12 +2814,12 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
tex.dirty = false;
}
- RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid();
+ RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid();
if (fd->msdf) {
Point2 cpos = p_pos;
- cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size;
- Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size;
- RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
+ cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size;
+ Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size;
+ RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
} else {
Point2 cpos = p_pos;
double scale = _font_get_scale(p_font_rid, p_size);
@@ -2765,8 +2832,8 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
- Vector2 gpos = gl.rect.position;
- Size2 csize = gl.rect.size;
+ Vector2 gpos = fgl.rect.position;
+ Size2 csize = fgl.rect.size;
if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) {
if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) {
double gl_scale = (double)p_size / (double)fd->fixed_size;
@@ -2780,9 +2847,9 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
cpos += gpos;
if (lcd_aa) {
- RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate);
+ RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate);
} else {
- RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
+ RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false);
}
}
}
@@ -2872,7 +2939,8 @@ void TextServerFallback::_font_remove_script_support_override(const RID &p_font_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->script_support_overrides.erase(p_script);
}
@@ -2894,7 +2962,8 @@ void TextServerFallback::_font_set_opentype_feature_overrides(const RID &p_font_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd));
fd->feature_overrides = p_overrides;
}
@@ -2916,7 +2985,8 @@ Dictionary TextServerFallback::_font_supported_variation_list(const RID &p_font_
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
- ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
+ FontForSizeFallback *ffsd = nullptr;
+ ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary());
return fd->supported_varaitions;
}
@@ -2953,7 +3023,7 @@ void TextServerFallback::_font_set_global_oversampling(double p_oversampling) {
/*************************************************************************/
void TextServerFallback::invalidate(ShapedTextDataFallback *p_shaped) {
- p_shaped->valid = false;
+ p_shaped->valid.clear();
p_shaped->sort_valid = false;
p_shaped->line_breaks_valid = false;
p_shaped->justification_ops_valid = false;
@@ -3192,7 +3262,7 @@ void TextServerFallback::_shaped_set_span_update_font(const RID &p_shaped, int64
span.font_size = p_size;
span.features = p_opentype_features;
- sd->valid = false;
+ sd->valid.clear();
}
bool TextServerFallback::_shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) {
@@ -3288,7 +3358,7 @@ bool TextServerFallback::_shaped_text_resize_object(const RID &p_shaped, const V
sd->objects[p_key].rect.size = p_size;
sd->objects[p_key].inline_align = p_inline_align;
sd->objects[p_key].baseline = p_baseline;
- if (sd->valid) {
+ if (sd->valid.is_set()) {
// Recalc string metrics.
sd->ascent = 0;
sd->descent = 0;
@@ -3431,7 +3501,7 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start
if (sd->parent != RID()) {
return _shaped_text_substr(sd->parent, p_start, p_length);
}
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
ERR_FAIL_COND_V(p_start < 0 || p_length < 0, RID());
@@ -3514,7 +3584,7 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start
_realign(new_sd);
}
- new_sd->valid = true;
+ new_sd->valid.set();
return shaped_owner.make_rid(new_sd);
}
@@ -3532,7 +3602,7 @@ double TextServerFallback::_shaped_text_fit_to_width(const RID &p_shaped, double
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
if (!sd->justification_ops_valid) {
@@ -3641,7 +3711,7 @@ double TextServerFallback::_shaped_text_tab_align(const RID &p_shaped, const Pac
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
if (!sd->line_breaks_valid) {
@@ -3697,7 +3767,7 @@ bool TextServerFallback::_shaped_text_update_breaks(const RID &p_shaped) {
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped);
}
@@ -3761,7 +3831,7 @@ bool TextServerFallback::_shaped_text_update_justification_ops(const RID &p_shap
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped);
}
if (!sd->line_breaks_valid) {
@@ -3940,7 +4010,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
ERR_FAIL_NULL_MSG(sd, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
_shaped_text_shape(p_shaped_line);
}
@@ -3967,7 +4037,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
Vector<ShapedTextDataFallback::Span> &spans = sd->spans;
if (sd->parent != RID()) {
ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent);
- ERR_FAIL_COND(!parent_sd->valid);
+ ERR_FAIL_COND(!parent_sd->valid.is_set());
spans = parent_sd->spans;
}
@@ -4161,7 +4231,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
- if (sd->valid) {
+ if (sd->valid.is_set()) {
return true;
}
@@ -4178,7 +4248,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
sd->glyphs.clear();
if (sd->text.length() == 0) {
- sd->valid = true;
+ sd->valid.set();
return true;
}
@@ -4307,16 +4377,15 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
// Align embedded objects to baseline.
_realign(sd);
- sd->valid = true;
- return sd->valid;
+ sd->valid.set();
+ return sd->valid.is_set();
}
bool TextServerFallback::_shaped_text_is_ready(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL_V(sd, false);
- MutexLock lock(sd->mutex);
- return sd->valid;
+ return sd->valid.is_set();
}
const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) const {
@@ -4324,7 +4393,7 @@ const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) co
ERR_FAIL_NULL_V(sd, nullptr);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->glyphs.ptr();
@@ -4335,7 +4404,7 @@ int64_t TextServerFallback::_shaped_text_get_glyph_count(const RID &p_shaped) co
ERR_FAIL_NULL_V(sd, 0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->glyphs.size();
@@ -4346,7 +4415,7 @@ const Glyph *TextServerFallback::_shaped_text_sort_logical(const RID &p_shaped)
ERR_FAIL_NULL_V(sd, nullptr);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
@@ -4380,7 +4449,7 @@ Rect2 TextServerFallback::_shaped_text_get_object_rect(const RID &p_shaped, cons
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2());
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->objects[p_key].rect;
@@ -4417,7 +4486,7 @@ Size2 TextServerFallback::_shaped_text_get_size(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, Size2());
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) {
@@ -4432,7 +4501,7 @@ double TextServerFallback::_shaped_text_get_ascent(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->ascent + sd->extra_spacing[SPACING_TOP];
@@ -4443,7 +4512,7 @@ double TextServerFallback::_shaped_text_get_descent(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->descent + sd->extra_spacing[SPACING_BOTTOM];
@@ -4454,7 +4523,7 @@ double TextServerFallback::_shaped_text_get_width(const RID &p_shaped) const {
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return Math::ceil(sd->width);
@@ -4465,7 +4534,7 @@ double TextServerFallback::_shaped_text_get_underline_position(const RID &p_shap
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
@@ -4477,7 +4546,7 @@ double TextServerFallback::_shaped_text_get_underline_thickness(const RID &p_sha
ERR_FAIL_NULL_V(sd, 0.0);
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
@@ -4489,7 +4558,7 @@ PackedInt32Array TextServerFallback::_shaped_text_get_character_breaks(const RID
ERR_FAIL_NULL_V(sd, PackedInt32Array());
MutexLock lock(sd->mutex);
- if (!sd->valid) {
+ if (!sd->valid.is_set()) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
@@ -4626,8 +4695,13 @@ PackedInt32Array TextServerFallback::_string_get_word_breaks(const String &p_str
return ret;
}
+void TextServerFallback::_update_settings() {
+ lcd_subpixel_layout.set((TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"));
+}
+
TextServerFallback::TextServerFallback() {
_insert_feature_sets();
+ ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextServerFallback::_update_settings));
};
void TextServerFallback::_cleanup() {
diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h
index 1b76c6fa0f..ee1f72401f 100644
--- a/modules/text_server_fb/text_server_fb.h
+++ b/modules/text_server_fb/text_server_fb.h
@@ -72,6 +72,7 @@
#include <godot_cpp/templates/hash_map.hpp>
#include <godot_cpp/templates/hash_set.hpp>
#include <godot_cpp/templates/rid_owner.hpp>
+#include <godot_cpp/templates/safe_refcount.hpp>
#include <godot_cpp/templates/vector.hpp>
using namespace godot;
@@ -83,6 +84,7 @@ using namespace godot;
#include "core/object/worker_thread_pool.h"
#include "core/templates/hash_map.h"
#include "core/templates/rid_owner.h"
+#include "core/templates/safe_refcount.h"
#include "scene/resources/image_texture.h"
#include "servers/text/text_server_extension.h"
@@ -116,6 +118,9 @@ class TextServerFallback : public TextServerExtension {
HashMap<StringName, int32_t> feature_sets;
HashMap<int32_t, StringName> feature_sets_inv;
+ SafeNumeric<TextServer::FontLCDSubpixelLayout> lcd_subpixel_layout{ TextServer::FontLCDSubpixelLayout::FONT_LCD_SUBPIXEL_LAYOUT_NONE };
+ void _update_settings();
+
void _insert_feature_sets();
_FORCE_INLINE_ void _insert_feature(const StringName &p_name, int32_t p_tag);
@@ -278,7 +283,7 @@ class TextServerFallback : public TextServerExtension {
int extra_spacing[4] = { 0, 0, 0, 0 };
double baseline_offset = 0.0;
- HashMap<Vector2i, FontForSizeFallback *, VariantHasher, VariantComparator> cache;
+ HashMap<Vector2i, FontForSizeFallback *> cache;
bool face_init = false;
Dictionary supported_varaitions;
@@ -308,8 +313,8 @@ class TextServerFallback : public TextServerExtension {
#ifdef MODULE_FREETYPE_ENABLED
_FORCE_INLINE_ FontGlyph rasterize_bitmap(FontForSizeFallback *p_data, int p_rect_margin, FT_Bitmap p_bitmap, int p_yofs, int p_xofs, const Vector2 &p_advance, bool p_bgra) const;
#endif
- _FORCE_INLINE_ bool _ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const;
- _FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size) const;
+ _FORCE_INLINE_ bool _ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const;
+ _FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size) const;
_FORCE_INLINE_ void _font_clear_cache(FontFallback *p_font_data);
static void _generateMTSDF_threaded(void *p_td, uint32_t p_y);
@@ -432,7 +437,7 @@ class TextServerFallback : public TextServerExtension {
/* Shaped data */
TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction.
- bool valid = false; // String is shaped.
+ SafeFlag valid{ false }; // String is shaped.
bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted).
bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string.
bool sort_valid = false;
diff --git a/platform/SCsub b/platform/SCsub
index b07023efed..cdaa6074ba 100644
--- a/platform/SCsub
+++ b/platform/SCsub
@@ -15,12 +15,12 @@ def export_icon_builder(target, source, env):
src_path = Path(str(source[0]))
src_name = src_path.stem
platform = src_path.parent.parent.stem
- with open(str(source[0]), "rb") as file:
- svg = "".join([f"\\{hex(x)[1:]}" for x in file.read()])
+ with open(str(source[0]), "r") as file:
+ svg = file.read()
with methods.generated_wrapper(target, prefix=platform) as file:
file.write(
f"""\
-static const char *_{platform}_{src_name}_svg = "{svg}";
+static const char *_{platform}_{src_name}_svg = {methods.to_raw_cstring(svg)};
"""
)
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 8dc0e869d0..5bb520bd73 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -455,11 +455,15 @@ Size2i DisplayServerAndroid::window_get_size_with_decorations(DisplayServer::Win
}
void DisplayServerAndroid::window_set_mode(DisplayServer::WindowMode p_mode, DisplayServer::WindowID p_window) {
- // Not supported on Android.
+ OS_Android::get_singleton()->get_godot_java()->enable_immersive_mode(p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN);
}
DisplayServer::WindowMode DisplayServerAndroid::window_get_mode(DisplayServer::WindowID p_window) const {
- return WINDOW_MODE_FULLSCREEN;
+ if (OS_Android::get_singleton()->get_godot_java()->is_in_immersive_mode()) {
+ return WINDOW_MODE_FULLSCREEN;
+ } else {
+ return WINDOW_MODE_MAXIMIZED;
+ }
}
bool DisplayServerAndroid::window_is_maximize_allowed(DisplayServer::WindowID p_window) const {
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 0fdaca4839..f8ac591a78 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -2002,7 +2002,7 @@ String EditorExportPlatformAndroid::get_device_architecture(int p_index) const {
return devices[p_index].architecture;
}
-Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
+Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER);
String can_export_error;
@@ -2024,11 +2024,11 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset,
}
const bool use_wifi_for_remote_debug = EDITOR_GET("export/android/use_wifi_for_remote_debug");
- const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT);
+ const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT);
const bool use_reverse = devices[p_device].api_level >= 21 && !use_wifi_for_remote_debug;
if (use_reverse) {
- p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST;
+ p_debug_flags.set_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST);
}
String tmp_export_path = EditorPaths::get_singleton()->get_cache_dir().path_join("tmpexport." + uitos(OS::get_singleton()->get_unix_time()) + ".apk");
@@ -2107,7 +2107,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset,
OS::get_singleton()->execute(adb, args, &output, &rv, true);
print_verbose(output);
- if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) {
int dbg_port = EDITOR_GET("network/debug/remote_port");
args.clear();
args.push_back("-s");
@@ -2122,7 +2122,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset,
print_line("Reverse result: " + itos(rv));
}
- if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) {
int fs_port = EDITOR_GET("filesystem/file_server/port");
args.clear();
@@ -2667,7 +2667,7 @@ Error EditorExportPlatformAndroid::save_apk_expansion_file(const Ref<EditorExpor
return err;
}
-void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) {
+void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, Vector<uint8_t> &r_command_line_flags) {
String cmdline = p_preset->get("command_line/extra_args");
Vector<String> command_line_strings = cmdline.strip_edges().split(" ");
for (int i = 0; i < command_line_strings.size(); i++) {
@@ -2677,7 +2677,7 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP
}
}
- gen_export_flags(command_line_strings, p_flags);
+ command_line_strings.append_array(gen_export_flags(p_flags));
bool apk_expansion = p_preset->get("apk_expansion/enable");
if (apk_expansion) {
@@ -2700,7 +2700,7 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP
bool immersive = p_preset->get("screen/immersive_mode");
if (immersive) {
- command_line_strings.push_back("--use_immersive");
+ command_line_strings.push_back("--fullscreen");
}
bool debug_opengl = p_preset->get("graphics/opengl_debug");
@@ -3000,13 +3000,13 @@ bool EditorExportPlatformAndroid::_is_clean_build_required(const Ref<EditorExpor
return have_plugins_changed || has_build_dir_changed || first_build;
}
-Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
int export_format = int(p_preset->get("gradle_build/export_format"));
bool should_sign = p_preset->get("package/signed");
return export_project_helper(p_preset, p_debug, p_path, export_format, should_sign, p_flags);
}
-Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, int p_flags) {
+Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
const String base_dir = p_path.get_base_dir();
@@ -3022,7 +3022,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
bool use_gradle_build = bool(p_preset->get("gradle_build/use_gradle_build"));
String gradle_build_directory = use_gradle_build ? ExportTemplateManager::get_android_build_directory(p_preset) : "";
- bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG);
+ bool p_give_internet = p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT) || p_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG);
bool apk_expansion = p_preset->get("apk_expansion/enable");
Vector<ABI> enabled_abis = get_enabled_abis(p_preset);
@@ -3127,7 +3127,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
user_data.assets_directory = assets_directory;
user_data.libs_directory = gradle_build_directory.path_join("libs");
user_data.debug = p_debug;
- if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) {
err = export_project_files(p_preset, p_debug, ignore_apk_file, &user_data, copy_gradle_so);
} else {
err = export_project_files(p_preset, p_debug, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so);
@@ -3500,7 +3500,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
}
err = OK;
- if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) {
APKExportData ed;
ed.ep = &ep;
ed.apk = unaligned_apk;
diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h
index 97bbd0c7bc..708288fbf4 100644
--- a/platform/android/export/export_plugin.h
+++ b/platform/android/export/export_plugin.h
@@ -214,7 +214,7 @@ public:
virtual String get_device_architecture(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual Ref<Texture2D> get_run_icon() const override;
@@ -242,7 +242,7 @@ public:
Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
- void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags);
+ void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, Vector<uint8_t> &r_command_line_flags);
Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &export_path, EditorProgress &ep);
@@ -253,9 +253,9 @@ public:
static String join_list(const List<String> &p_parts, const String &p_separator);
static String join_abis(const Vector<ABI> &p_parts, const String &p_separator, bool p_use_arch);
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
- Error export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, int p_flags);
+ Error export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, BitField<EditorExportPlatform::DebugFlags> p_flags);
virtual void get_platform_features(List<String> *r_features) const override;
diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp
index ae336d6f9d..59b669eabb 100644
--- a/platform/android/file_access_android.cpp
+++ b/platform/android/file_access_android.cpp
@@ -113,87 +113,6 @@ bool FileAccessAndroid::eof_reached() const {
return eof;
}
-uint8_t FileAccessAndroid::get_8() const {
- if (pos >= len) {
- eof = true;
- return 0;
- }
-
- uint8_t byte;
- AAsset_read(asset, &byte, 1);
- pos++;
- return byte;
-}
-
-uint16_t FileAccessAndroid::get_16() const {
- if (pos >= len) {
- eof = true;
- return 0;
- }
-
- uint16_t bytes = 0;
- int r = AAsset_read(asset, &bytes, 2);
-
- if (r >= 0) {
- pos += r;
- if (pos >= len) {
- eof = true;
- }
- }
-
- if (big_endian) {
- bytes = BSWAP16(bytes);
- }
-
- return bytes;
-}
-
-uint32_t FileAccessAndroid::get_32() const {
- if (pos >= len) {
- eof = true;
- return 0;
- }
-
- uint32_t bytes = 0;
- int r = AAsset_read(asset, &bytes, 4);
-
- if (r >= 0) {
- pos += r;
- if (pos >= len) {
- eof = true;
- }
- }
-
- if (big_endian) {
- bytes = BSWAP32(bytes);
- }
-
- return bytes;
-}
-
-uint64_t FileAccessAndroid::get_64() const {
- if (pos >= len) {
- eof = true;
- return 0;
- }
-
- uint64_t bytes = 0;
- int r = AAsset_read(asset, &bytes, 8);
-
- if (r >= 0) {
- pos += r;
- if (pos >= len) {
- eof = true;
- }
- }
-
- if (big_endian) {
- bytes = BSWAP64(bytes);
- }
-
- return bytes;
-}
-
uint64_t FileAccessAndroid::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
@@ -209,6 +128,7 @@ uint64_t FileAccessAndroid::get_buffer(uint8_t *p_dst, uint64_t p_length) const
pos = len;
}
}
+
return r;
}
@@ -220,19 +140,7 @@ void FileAccessAndroid::flush() {
ERR_FAIL();
}
-void FileAccessAndroid::store_8(uint8_t p_dest) {
- ERR_FAIL();
-}
-
-void FileAccessAndroid::store_16(uint16_t p_dest) {
- ERR_FAIL();
-}
-
-void FileAccessAndroid::store_32(uint32_t p_dest) {
- ERR_FAIL();
-}
-
-void FileAccessAndroid::store_64(uint64_t p_dest) {
+void FileAccessAndroid::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL();
}
diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h
index b465a92c78..3224ab50b9 100644
--- a/platform/android/file_access_android.h
+++ b/platform/android/file_access_android.h
@@ -68,19 +68,12 @@ public:
virtual bool eof_reached() const override; // reading passed EOF
virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; }
- virtual uint8_t get_8() const override; // get a byte
- virtual uint16_t get_16() const override;
- virtual uint32_t get_32() const override;
- virtual uint64_t get_64() const override;
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; // get last error
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; // store a byte
- virtual void store_16(uint16_t p_dest) override;
- virtual void store_32(uint32_t p_dest) override;
- virtual void store_64(uint64_t p_dest) override;
+ virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_path) override; // return true if a file exists
diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp
index 9ae48dfb10..8b52a00ed8 100644
--- a/platform/android/file_access_filesystem_jandroid.cpp
+++ b/platform/android/file_access_filesystem_jandroid.cpp
@@ -169,43 +169,6 @@ void FileAccessFilesystemJAndroid::_set_eof(bool eof) {
}
}
-uint8_t FileAccessFilesystemJAndroid::get_8() const {
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- uint8_t byte;
- get_buffer(&byte, 1);
- return byte;
-}
-
-uint16_t FileAccessFilesystemJAndroid::get_16() const {
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- uint16_t bytes = 0;
- get_buffer(reinterpret_cast<uint8_t *>(&bytes), 2);
- if (big_endian) {
- bytes = BSWAP16(bytes);
- }
- return bytes;
-}
-
-uint32_t FileAccessFilesystemJAndroid::get_32() const {
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- uint32_t bytes = 0;
- get_buffer(reinterpret_cast<uint8_t *>(&bytes), 4);
- if (big_endian) {
- bytes = BSWAP32(bytes);
- }
- return bytes;
-}
-
-uint64_t FileAccessFilesystemJAndroid::get_64() const {
- ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
- uint64_t bytes = 0;
- get_buffer(reinterpret_cast<uint8_t *>(&bytes), 8);
- if (big_endian) {
- bytes = BSWAP64(bytes);
- }
- return bytes;
-}
-
String FileAccessFilesystemJAndroid::get_line() const {
ERR_FAIL_COND_V_MSG(!is_open(), String(), "File must be opened before use.");
@@ -271,31 +234,6 @@ uint64_t FileAccessFilesystemJAndroid::get_buffer(uint8_t *p_dst, uint64_t p_len
}
}
-void FileAccessFilesystemJAndroid::store_8(uint8_t p_dest) {
- store_buffer(&p_dest, 1);
-}
-
-void FileAccessFilesystemJAndroid::store_16(uint16_t p_dest) {
- if (big_endian) {
- p_dest = BSWAP16(p_dest);
- }
- store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 2);
-}
-
-void FileAccessFilesystemJAndroid::store_32(uint32_t p_dest) {
- if (big_endian) {
- p_dest = BSWAP32(p_dest);
- }
- store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 4);
-}
-
-void FileAccessFilesystemJAndroid::store_64(uint64_t p_dest) {
- if (big_endian) {
- p_dest = BSWAP64(p_dest);
- }
- store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 8);
-}
-
void FileAccessFilesystemJAndroid::store_buffer(const uint8_t *p_src, uint64_t p_length) {
if (_file_write) {
ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use.");
diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h
index 2795ac02ac..1345b72fa6 100644
--- a/platform/android/file_access_filesystem_jandroid.h
+++ b/platform/android/file_access_filesystem_jandroid.h
@@ -78,20 +78,12 @@ public:
virtual bool eof_reached() const override; ///< reading passed EOF
virtual Error resize(int64_t p_length) override;
- virtual uint8_t get_8() const override; ///< get a byte
- virtual uint16_t get_16() const override;
- virtual uint32_t get_32() const override;
- virtual uint64_t get_64() const override;
virtual String get_line() const override; ///< get a line
virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
virtual Error get_error() const override; ///< get last error
virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
- virtual void store_16(uint16_t p_dest) override;
- virtual void store_32(uint32_t p_dest) override;
- virtual void store_64(uint64_t p_dest) override;
virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override;
virtual bool file_exists(const String &p_path) override; ///< return true if a file exists
diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle
index f9a3e10680..b8b4233636 100644
--- a/platform/android/java/editor/build.gradle
+++ b/platform/android/java/editor/build.gradle
@@ -37,7 +37,7 @@ ext {
// Return the keystore file used for signing the release build.
getGodotKeystoreFile = { ->
def keyStore = System.getenv("GODOT_ANDROID_SIGN_KEYSTORE")
- if (keyStore == null) {
+ if (keyStore == null || keyStore.isEmpty()) {
return null
}
return file(keyStore)
diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml
index c7d14a3f49..a875745860 100644
--- a/platform/android/java/editor/src/main/AndroidManifest.xml
+++ b/platform/android/java/editor/src/main/AndroidManifest.xml
@@ -42,6 +42,7 @@
android:name=".GodotEditor"
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
android:exported="true"
+ android:icon="@mipmap/icon"
android:launchMode="singleTask"
android:screenOrientation="userLandscape">
<layout
@@ -59,9 +60,11 @@
android:name=".GodotGame"
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
android:exported="false"
- android:label="@string/godot_project_name_string"
+ android:icon="@mipmap/ic_play_window"
+ android:label="@string/godot_game_activity_name"
android:launchMode="singleTask"
android:process=":GodotGame"
+ android:supportsPictureInPicture="true"
android:screenOrientation="userLandscape">
<layout
android:defaultWidth="@dimen/editor_default_window_width"
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
new file mode 100644
index 0000000000..b16e62149a
--- /dev/null
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt
@@ -0,0 +1,192 @@
+/**************************************************************************/
+/* EditorMessageDispatcher.kt */
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+package org.godotengine.editor
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.os.Handler
+import android.os.Message
+import android.os.Messenger
+import android.os.RemoteException
+import android.util.Log
+import java.util.concurrent.ConcurrentHashMap
+
+/**
+ * Used by the [GodotEditor] classes to dispatch messages across processes.
+ */
+internal class EditorMessageDispatcher(private val editor: GodotEditor) {
+
+ companion object {
+ private val TAG = EditorMessageDispatcher::class.java.simpleName
+
+ /**
+ * Extra used to pass the message dispatcher payload through an [Intent]
+ */
+ const val EXTRA_MSG_DISPATCHER_PAYLOAD = "message_dispatcher_payload"
+
+ /**
+ * Key used to pass the editor id through a [Bundle]
+ */
+ private const val KEY_EDITOR_ID = "editor_id"
+
+ /**
+ * Key used to pass the editor messenger through a [Bundle]
+ */
+ private const val KEY_EDITOR_MESSENGER = "editor_messenger"
+
+ /**
+ * Requests the recipient to quit right away.
+ */
+ private const val MSG_FORCE_QUIT = 0
+
+ /**
+ * Requests the recipient to store the passed [android.os.Messenger] instance.
+ */
+ private const val MSG_REGISTER_MESSENGER = 1
+ }
+
+ private val recipientsMessengers = ConcurrentHashMap<Int, Messenger>()
+
+ @SuppressLint("HandlerLeak")
+ private val dispatcherHandler = object : Handler() {
+ override fun handleMessage(msg: Message) {
+ when (msg.what) {
+ MSG_FORCE_QUIT -> editor.finish()
+
+ MSG_REGISTER_MESSENGER -> {
+ val editorId = msg.arg1
+ val messenger = msg.replyTo
+ registerMessenger(editorId, messenger)
+ }
+
+ else -> super.handleMessage(msg)
+ }
+ }
+ }
+
+ /**
+ * Request the window with the given [editorId] to force quit.
+ */
+ fun requestForceQuit(editorId: Int): Boolean {
+ val messenger = recipientsMessengers[editorId] ?: return false
+ return try {
+ Log.v(TAG, "Requesting 'forceQuit' for $editorId")
+ val msg = Message.obtain(null, MSG_FORCE_QUIT)
+ messenger.send(msg)
+ true
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Error requesting 'forceQuit' to $editorId", e)
+ recipientsMessengers.remove(editorId)
+ false
+ }
+ }
+
+ /**
+ * Utility method to register a receiver messenger.
+ */
+ private fun registerMessenger(editorId: Int, messenger: Messenger?, messengerDeathCallback: Runnable? = null) {
+ try {
+ if (messenger == null) {
+ Log.w(TAG, "Invalid 'replyTo' payload")
+ } else if (messenger.binder.isBinderAlive) {
+ messenger.binder.linkToDeath({
+ Log.v(TAG, "Removing messenger for $editorId")
+ recipientsMessengers.remove(editorId)
+ messengerDeathCallback?.run()
+ }, 0)
+ recipientsMessengers[editorId] = messenger
+ }
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Unable to register messenger from $editorId", e)
+ recipientsMessengers.remove(editorId)
+ }
+ }
+
+ /**
+ * Utility method to register a [Messenger] attached to this handler with a host.
+ *
+ * This is done so that the host can send request to the editor instance attached to this handle.
+ *
+ * Note that this is only done when the editor instance is internal (not exported) to prevent
+ * arbitrary apps from having the ability to send requests.
+ */
+ private fun registerSelfTo(pm: PackageManager, host: Messenger?, selfId: Int) {
+ try {
+ if (host == null || !host.binder.isBinderAlive) {
+ Log.v(TAG, "Host is unavailable")
+ return
+ }
+
+ val activityInfo = pm.getActivityInfo(editor.componentName, 0)
+ if (activityInfo.exported) {
+ Log.v(TAG, "Not registering self to host as we're exported")
+ return
+ }
+
+ Log.v(TAG, "Registering self $selfId to host")
+ val msg = Message.obtain(null, MSG_REGISTER_MESSENGER)
+ msg.arg1 = selfId
+ msg.replyTo = Messenger(dispatcherHandler)
+ host.send(msg)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Unable to register self with host", e)
+ }
+ }
+
+ /**
+ * Parses the starting intent and retrieve an editor messenger if available
+ */
+ fun parseStartIntent(pm: PackageManager, intent: Intent) {
+ val messengerBundle = intent.getBundleExtra(EXTRA_MSG_DISPATCHER_PAYLOAD) ?: return
+
+ // Retrieve the sender messenger payload and store it. This can be used to communicate back
+ // to the sender.
+ val senderId = messengerBundle.getInt(KEY_EDITOR_ID)
+ val senderMessenger: Messenger? = messengerBundle.getParcelable(KEY_EDITOR_MESSENGER)
+ registerMessenger(senderId, senderMessenger)
+
+ // Register ourselves to the sender so that it can communicate with us.
+ registerSelfTo(pm, senderMessenger, editor.getEditorWindowInfo().windowId)
+ }
+
+ /**
+ * Returns the payload used by the [EditorMessageDispatcher] class to establish an IPC bridge
+ * across editor instances.
+ */
+ fun getMessageDispatcherPayload(): Bundle {
+ return Bundle().apply {
+ putInt(KEY_EDITOR_ID, editor.getEditorWindowInfo().windowId)
+ putParcelable(KEY_EDITOR_MESSENGER, Messenger(dispatcherHandler))
+ }
+ }
+}
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt
index 0da1d01aed..d3daa1dbbc 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt
@@ -31,23 +31,24 @@
package org.godotengine.editor
/**
- * Specifies the policy for adjacent launches.
+ * Specifies the policy for launches.
*/
-enum class LaunchAdjacentPolicy {
+enum class LaunchPolicy {
/**
- * Adjacent launches are disabled.
+ * Launch policy is determined by the editor settings or based on the device and screen metrics.
*/
- DISABLED,
+ AUTO,
+
/**
- * Adjacent launches are enabled / disabled based on the device and screen metrics.
+ * Launches happen in the same window.
*/
- AUTO,
+ SAME,
/**
* Adjacent launches are enabled.
*/
- ENABLED
+ ADJACENT
}
/**
@@ -57,12 +58,14 @@ data class EditorWindowInfo(
val windowClassName: String,
val windowId: Int,
val processNameSuffix: String,
- val launchAdjacentPolicy: LaunchAdjacentPolicy = LaunchAdjacentPolicy.DISABLED
+ val launchPolicy: LaunchPolicy = LaunchPolicy.SAME,
+ val supportsPiPMode: Boolean = false
) {
constructor(
windowClass: Class<*>,
windowId: Int,
processNameSuffix: String,
- launchAdjacentPolicy: LaunchAdjacentPolicy = LaunchAdjacentPolicy.DISABLED
- ) : this(windowClass.name, windowId, processNameSuffix, launchAdjacentPolicy)
+ launchPolicy: LaunchPolicy = LaunchPolicy.SAME,
+ supportsPiPMode: Boolean = false
+ ) : this(windowClass.name, windowId, processNameSuffix, launchPolicy, supportsPiPMode)
}
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
index 9cc133046b..1995a38c2a 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
@@ -32,6 +32,7 @@ package org.godotengine.editor
import android.Manifest
import android.app.ActivityManager
+import android.app.ActivityOptions
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -39,6 +40,7 @@ import android.content.pm.PackageManager
import android.os.*
import android.util.Log
import android.view.View
+import android.view.WindowManager
import android.widget.Toast
import androidx.annotation.CallSuper
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@@ -69,17 +71,26 @@ open class GodotEditor : GodotActivity() {
private const val WAIT_FOR_DEBUGGER = false
- private const val EXTRA_COMMAND_LINE_PARAMS = "command_line_params"
+ @JvmStatic
+ protected val EXTRA_COMMAND_LINE_PARAMS = "command_line_params"
+ @JvmStatic
+ protected val EXTRA_PIP_AVAILABLE = "pip_available"
+ @JvmStatic
+ protected val EXTRA_LAUNCH_IN_PIP = "launch_in_pip_requested"
// 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"
// Info for the various classes used by the editor
internal val EDITOR_MAIN_INFO = EditorWindowInfo(GodotEditor::class.java, 777, "")
- internal val RUN_GAME_INFO = EditorWindowInfo(GodotGame::class.java, 667, ":GodotGame", LaunchAdjacentPolicy.AUTO)
+ internal val RUN_GAME_INFO = EditorWindowInfo(GodotGame::class.java, 667, ":GodotGame", LaunchPolicy.AUTO, true)
/**
* Sets of constants to specify the window to use to run the project.
@@ -90,16 +101,34 @@ open class GodotEditor : GodotActivity() {
private const val ANDROID_WINDOW_AUTO = 0
private const val ANDROID_WINDOW_SAME_AS_EDITOR = 1
private const val ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR = 2
+
+ /**
+ * Sets of constants to specify the Play window PiP mode.
+ *
+ * Should match the values in `editor/editor_settings.cpp'` for the
+ * 'run/window_placement/play_window_pip_mode' setting.
+ */
+ private const val PLAY_WINDOW_PIP_DISABLED = 0
+ private const val PLAY_WINDOW_PIP_ENABLED = 1
+ private const val PLAY_WINDOW_PIP_ENABLED_FOR_SAME_AS_EDITOR = 2
}
+ private val editorMessageDispatcher = EditorMessageDispatcher(this)
private val commandLineParams = ArrayList<String>()
private val editorLoadingIndicator: View? by lazy { findViewById(R.id.editor_loading_indicator) }
override fun getGodotAppLayout() = R.layout.godot_editor_layout
+ internal open fun getEditorWindowInfo() = EDITOR_MAIN_INFO
+
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
+ // Prevent the editor window from showing in the display cutout
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && getEditorWindowInfo() == EDITOR_MAIN_INFO) {
+ window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
+ }
+
// 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))
@@ -108,6 +137,8 @@ open class GodotEditor : GodotActivity() {
Log.d(TAG, "Starting intent $intent with parameters ${params.contentToString()}")
updateCommandLineParams(params?.asList() ?: emptyList())
+ editorMessageDispatcher.parseStartIntent(packageManager, intent)
+
if (BuildConfig.BUILD_TYPE == "dev" && WAIT_FOR_DEBUGGER) {
Debug.waitForDebugger()
}
@@ -189,35 +220,81 @@ open class GodotEditor : GodotActivity() {
}
}
- override fun onNewGodotInstanceRequested(args: Array<String>): Int {
- val editorWindowInfo = getEditorWindowInfo(args)
+ protected fun getNewGodotInstanceIntent(editorWindowInfo: EditorWindowInfo, args: Array<String>): Intent {
+ val updatedArgs = if (editorWindowInfo == EDITOR_MAIN_INFO &&
+ godot?.isInImmersiveMode() == true &&
+ !args.contains(FULLSCREEN_ARG) &&
+ !args.contains(FULLSCREEN_ARG_SHORT)
+ ) {
+ // If we're launching an editor window (project manager or editor) and we're in
+ // fullscreen mode, we want to remain in fullscreen mode.
+ // This doesn't apply to the play / game window since for that window fullscreen is
+ // controlled by the game logic.
+ args + FULLSCREEN_ARG
+ } else {
+ args
+ }
- // Launch a new activity
val newInstance = Intent()
.setComponent(ComponentName(this, editorWindowInfo.windowClassName))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .putExtra(EXTRA_COMMAND_LINE_PARAMS, args)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- if (editorWindowInfo.launchAdjacentPolicy == LaunchAdjacentPolicy.ENABLED ||
- (editorWindowInfo.launchAdjacentPolicy == LaunchAdjacentPolicy.AUTO && shouldGameLaunchAdjacent())) {
+ .putExtra(EXTRA_COMMAND_LINE_PARAMS, updatedArgs)
+
+ val launchPolicy = resolveLaunchPolicyIfNeeded(editorWindowInfo.launchPolicy)
+ val isPiPAvailable = if (editorWindowInfo.supportsPiPMode && hasPiPSystemFeature()) {
+ val pipMode = getPlayWindowPiPMode()
+ pipMode == PLAY_WINDOW_PIP_ENABLED ||
+ (pipMode == PLAY_WINDOW_PIP_ENABLED_FOR_SAME_AS_EDITOR && launchPolicy == LaunchPolicy.SAME)
+ } else {
+ false
+ }
+ newInstance.putExtra(EXTRA_PIP_AVAILABLE, isPiPAvailable)
+
+ if (launchPolicy == LaunchPolicy.ADJACENT) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Log.v(TAG, "Adding flag for adjacent launch")
newInstance.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT)
}
+ } else if (launchPolicy == LaunchPolicy.SAME) {
+ if (isPiPAvailable &&
+ (updatedArgs.contains(BREAKPOINTS_ARG) || updatedArgs.contains(BREAKPOINTS_ARG_SHORT))) {
+ Log.v(TAG, "Launching in PiP mode because of breakpoints")
+ newInstance.putExtra(EXTRA_LAUNCH_IN_PIP, true)
+ }
+ }
+
+ return newInstance
+ }
+
+ override fun onNewGodotInstanceRequested(args: Array<String>): Int {
+ val editorWindowInfo = getEditorWindowInfo(args)
+
+ // Launch a new activity
+ val sourceView = godotFragment?.view
+ val activityOptions = if (sourceView == null) {
+ null
+ } else {
+ val startX = sourceView.width / 2
+ val startY = sourceView.height / 2
+ ActivityOptions.makeScaleUpAnimation(sourceView, startX, startY, 0, 0)
}
+
+ val newInstance = getNewGodotInstanceIntent(editorWindowInfo, args)
if (editorWindowInfo.windowClassName == javaClass.name) {
Log.d(TAG, "Restarting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}")
val godot = godot
if (godot != null) {
godot.destroyAndKillProcess {
- ProcessPhoenix.triggerRebirth(this, newInstance)
+ ProcessPhoenix.triggerRebirth(this, activityOptions?.toBundle(), newInstance)
}
} else {
- ProcessPhoenix.triggerRebirth(this, newInstance)
+ ProcessPhoenix.triggerRebirth(this, activityOptions?.toBundle(), newInstance)
}
} else {
Log.d(TAG, "Starting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}")
newInstance.putExtra(EXTRA_NEW_LAUNCH, true)
- startActivity(newInstance)
+ .putExtra(EditorMessageDispatcher.EXTRA_MSG_DISPATCHER_PAYLOAD, editorMessageDispatcher.getMessageDispatcherPayload())
+ startActivity(newInstance, activityOptions?.toBundle())
}
return editorWindowInfo.windowId
}
@@ -231,6 +308,12 @@ open class GodotEditor : GodotActivity() {
return true
}
+ // Send an inter-process message to request the target editor window to force quit.
+ if (editorMessageDispatcher.requestForceQuit(editorWindowInfo.windowId)) {
+ return true
+ }
+
+ // Fallback to killing the target process.
val processName = packageName + editorWindowInfo.processNameSuffix
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val runningProcesses = activityManager.runningAppProcesses
@@ -285,29 +368,65 @@ open class GodotEditor : GodotActivity() {
java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/enable_pan_and_scale_gestures"))
/**
- * Whether we should launch the new godot instance in an adjacent window
- * @see https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT
+ * Retrieves the play window pip mode editor setting.
*/
- private fun shouldGameLaunchAdjacent(): Boolean {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- try {
- when (Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/android_window"))) {
- ANDROID_WINDOW_SAME_AS_EDITOR -> false
- ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR -> true
- else -> {
- // ANDROID_WINDOW_AUTO
- isInMultiWindowMode || isLargeScreen
+ private fun getPlayWindowPiPMode(): Int {
+ return try {
+ Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/play_window_pip_mode"))
+ } catch (e: NumberFormatException) {
+ PLAY_WINDOW_PIP_ENABLED_FOR_SAME_AS_EDITOR
+ }
+ }
+
+ /**
+ * If the launch policy is [LaunchPolicy.AUTO], resolve it into a specific policy based on the
+ * editor setting or device and screen metrics.
+ *
+ * If the launch policy is [LaunchPolicy.PIP] but PIP is not supported, fallback to the default
+ * launch policy.
+ */
+ private fun resolveLaunchPolicyIfNeeded(policy: LaunchPolicy): LaunchPolicy {
+ val inMultiWindowMode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ isInMultiWindowMode
+ } else {
+ false
+ }
+ val defaultLaunchPolicy = if (inMultiWindowMode || isLargeScreen) {
+ LaunchPolicy.ADJACENT
+ } else {
+ LaunchPolicy.SAME
+ }
+
+ 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
+ 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) {
- // Fall-back to the 'Auto' behavior
- isInMultiWindowMode || isLargeScreen
}
- } else {
- false
+
+ else -> {
+ policy
+ }
}
}
+ /**
+ * 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)
+
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Check if we got the MANAGE_EXTERNAL_STORAGE permission
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 2bcfba559c..6b4bf255f2 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,14 @@
package org.godotengine.editor
+import android.annotation.SuppressLint
+import android.app.PictureInPictureParams
+import android.content.Intent
+import android.graphics.Rect
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import android.view.View
import org.godotengine.godot.GodotLib
/**
@@ -37,7 +45,90 @@ import org.godotengine.godot.GodotLib
*/
class GodotGame : GodotEditor() {
- override fun getGodotAppLayout() = org.godotengine.godot.R.layout.godot_app_layout
+ companion object {
+ private val TAG = GodotGame::class.java.simpleName
+ }
+
+ private val gameViewSourceRectHint = Rect()
+ private val pipButton: View? by lazy {
+ findViewById(R.id.godot_pip_button)
+ }
+
+ private var pipAvailable = false
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val gameView = findViewById<View>(R.id.godot_fragment_container)
+ gameView?.addOnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
+ gameView.getGlobalVisibleRect(gameViewSourceRectHint)
+ }
+ }
+
+ pipButton?.setOnClickListener { enterPiPMode() }
+
+ handleStartIntent(intent)
+ }
+
+ override fun onNewIntent(newIntent: Intent) {
+ super.onNewIntent(newIntent)
+ handleStartIntent(newIntent)
+ }
+
+ private fun handleStartIntent(intent: Intent) {
+ pipAvailable = intent.getBooleanExtra(EXTRA_PIP_AVAILABLE, pipAvailable)
+ updatePiPButtonVisibility()
+
+ val pipLaunchRequested = intent.getBooleanExtra(EXTRA_LAUNCH_IN_PIP, false)
+ if (pipLaunchRequested) {
+ enterPiPMode()
+ }
+ }
+
+ private fun updatePiPButtonVisibility() {
+ pipButton?.visibility = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && pipAvailable && !isInPictureInPictureMode) {
+ View.VISIBLE
+ } else {
+ View.GONE
+ }
+ }
+
+ private fun enterPiPMode() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && pipAvailable) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val builder = PictureInPictureParams.Builder().setSourceRectHint(gameViewSourceRectHint)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ builder.setSeamlessResizeEnabled(false)
+ }
+ setPictureInPictureParams(builder.build())
+ }
+
+ Log.v(TAG, "Entering PiP mode")
+ enterPictureInPictureMode()
+ }
+ }
+
+ override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
+ super.onPictureInPictureModeChanged(isInPictureInPictureMode)
+ Log.v(TAG, "onPictureInPictureModeChanged: $isInPictureInPictureMode")
+ updatePiPButtonVisibility()
+ }
+
+ override fun onStop() {
+ super.onStop()
+
+ val isInPiPMode = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInPictureInPictureMode
+ if (isInPiPMode && !isFinishing) {
+ // We get in this state when PiP is closed, so we terminate the activity.
+ finish()
+ }
+ }
+
+ override fun getGodotAppLayout() = R.layout.godot_game_layout
+
+ override fun getEditorWindowInfo() = RUN_GAME_INFO
override fun overrideOrientationRequest() = false
diff --git a/platform/android/java/editor/src/main/res/drawable/ic_play_window_foreground.xml b/platform/android/java/editor/src/main/res/drawable/ic_play_window_foreground.xml
new file mode 100644
index 0000000000..41bc5475c8
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/drawable/ic_play_window_foreground.xml
@@ -0,0 +1,25 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:tint="#FFFFFF"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <group
+ android:scaleX="0.522"
+ android:scaleY="0.522"
+ android:translateX="5.736"
+ android:translateY="5.736">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M21.58,16.09l-1.09,-7.66C20.21,6.46 18.52,5 16.53,5H7.47C5.48,5 3.79,6.46 3.51,8.43l-1.09,7.66C2.2,17.63 3.39,19 4.94,19h0c0.68,0 1.32,-0.27 1.8,-0.75L9,16h6l2.25,2.25c0.48,0.48 1.13,0.75 1.8,0.75h0C20.61,19 21.8,17.63 21.58,16.09zM19.48,16.81C19.4,16.9 19.27,17 19.06,17c-0.15,0 -0.29,-0.06 -0.39,-0.16L15.83,14H8.17l-2.84,2.84C5.23,16.94 5.09,17 4.94,17c-0.21,0 -0.34,-0.1 -0.42,-0.19c-0.08,-0.09 -0.16,-0.23 -0.13,-0.44l1.09,-7.66C5.63,7.74 6.48,7 7.47,7h9.06c0.99,0 1.84,0.74 1.98,1.72l1.09,7.66C19.63,16.58 19.55,16.72 19.48,16.81z" />
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M9,8l-1,0l0,2l-2,0l0,1l2,0l0,2l1,0l0,-2l2,0l0,-1l-2,0z" />
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,12m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0" />
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M15,9m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0" />
+ </group>
+</vector>
diff --git a/platform/android/java/editor/src/main/res/drawable/outline_fullscreen_exit_48.xml b/platform/android/java/editor/src/main/res/drawable/outline_fullscreen_exit_48.xml
new file mode 100644
index 0000000000..c8b5a15d19
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/drawable/outline_fullscreen_exit_48.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:tint="#FFFFFF"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z" />
+
+</vector>
diff --git a/platform/android/java/editor/src/main/res/drawable/pip_button_activated_bg_drawable.xml b/platform/android/java/editor/src/main/res/drawable/pip_button_activated_bg_drawable.xml
new file mode 100644
index 0000000000..aeaa96ce54
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/drawable/pip_button_activated_bg_drawable.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+
+ <size
+ android:width="60dp"
+ android:height="60dp" />
+
+ <solid android:color="#44000000" />
+</shape>
diff --git a/platform/android/java/editor/src/main/res/drawable/pip_button_bg_drawable.xml b/platform/android/java/editor/src/main/res/drawable/pip_button_bg_drawable.xml
new file mode 100644
index 0000000000..e9b2959275
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/drawable/pip_button_bg_drawable.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:drawable="@drawable/pip_button_activated_bg_drawable" android:state_pressed="true" />
+ <item android:drawable="@drawable/pip_button_activated_bg_drawable" android:state_hovered="true" />
+
+ <item android:drawable="@drawable/pip_button_default_bg_drawable" />
+
+</selector>
diff --git a/platform/android/java/editor/src/main/res/drawable/pip_button_default_bg_drawable.xml b/platform/android/java/editor/src/main/res/drawable/pip_button_default_bg_drawable.xml
new file mode 100644
index 0000000000..a8919689fe
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/drawable/pip_button_default_bg_drawable.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+
+ <size
+ android:width="60dp"
+ android:height="60dp" />
+
+ <solid android:color="#13000000" />
+</shape>
diff --git a/platform/android/java/editor/src/main/res/layout/godot_game_layout.xml b/platform/android/java/editor/src/main/res/layout/godot_game_layout.xml
new file mode 100644
index 0000000000..d53787c87e
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/layout/godot_game_layout.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <FrameLayout
+ android:id="@+id/godot_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+ <ImageView
+ android:id="@+id/godot_pip_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="36dp"
+ android:contentDescription="@string/pip_button_description"
+ android:background="@drawable/pip_button_bg_drawable"
+ android:scaleType="center"
+ android:src="@drawable/outline_fullscreen_exit_48"
+ android:visibility="gone"
+ android:layout_gravity="end|top"
+ tools:visibility="visible" />
+
+</FrameLayout>
diff --git a/platform/android/java/editor/src/main/res/mipmap-anydpi-v26/ic_play_window.xml b/platform/android/java/editor/src/main/res/mipmap-anydpi-v26/ic_play_window.xml
new file mode 100644
index 0000000000..a3aabf2ee0
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/mipmap-anydpi-v26/ic_play_window.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@mipmap/icon_background"/>
+ <foreground android:drawable="@drawable/ic_play_window_foreground"/>
+</adaptive-icon>
diff --git a/platform/android/java/editor/src/main/res/mipmap-hdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-hdpi/ic_play_window.png
new file mode 100644
index 0000000000..a5ce40241f
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/mipmap-hdpi/ic_play_window.png
Binary files differ
diff --git a/platform/android/java/editor/src/main/res/mipmap-mdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-mdpi/ic_play_window.png
new file mode 100644
index 0000000000..147adb6127
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/mipmap-mdpi/ic_play_window.png
Binary files differ
diff --git a/platform/android/java/editor/src/main/res/mipmap-xhdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-xhdpi/ic_play_window.png
new file mode 100644
index 0000000000..0b1db1b923
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/mipmap-xhdpi/ic_play_window.png
Binary files differ
diff --git a/platform/android/java/editor/src/main/res/mipmap-xxhdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-xxhdpi/ic_play_window.png
new file mode 100644
index 0000000000..39d7450390
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/mipmap-xxhdpi/ic_play_window.png
Binary files differ
diff --git a/platform/android/java/editor/src/main/res/mipmap-xxxhdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-xxxhdpi/ic_play_window.png
new file mode 100644
index 0000000000..b7a09a15b5
--- /dev/null
+++ b/platform/android/java/editor/src/main/res/mipmap-xxxhdpi/ic_play_window.png
Binary files differ
diff --git a/platform/android/java/editor/src/main/res/values/dimens.xml b/platform/android/java/editor/src/main/res/values/dimens.xml
index 98bfe40179..1e486872e6 100644
--- a/platform/android/java/editor/src/main/res/values/dimens.xml
+++ b/platform/android/java/editor/src/main/res/values/dimens.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <dimen name="editor_default_window_height">600dp</dimen>
+ <dimen name="editor_default_window_height">640dp</dimen>
<dimen name="editor_default_window_width">1024dp</dimen>
</resources>
diff --git a/platform/android/java/editor/src/main/res/values/strings.xml b/platform/android/java/editor/src/main/res/values/strings.xml
index 909711ab18..0ad54ac3a1 100644
--- a/platform/android/java/editor/src/main/res/values/strings.xml
+++ b/platform/android/java/editor/src/main/res/values/strings.xml
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
+ <string name="godot_game_activity_name">Godot Play window</string>
<string name="denied_storage_permission_error_msg">Missing storage access permission!</string>
+ <string name="pip_button_description">Button used to toggle picture-in-picture mode for the Play window</string>
</resources>
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 49e8ffb008..38bd336e2d 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -42,13 +42,16 @@ import android.hardware.Sensor
import android.hardware.SensorManager
import android.os.*
import android.util.Log
+import android.util.TypedValue
import android.view.*
import android.widget.FrameLayout
import androidx.annotation.Keep
import androidx.annotation.StringRes
import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsAnimationCompat
import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
import com.google.android.vending.expansion.downloader.*
import org.godotengine.godot.error.Error
import org.godotengine.godot.input.GodotEditText
@@ -105,36 +108,26 @@ class Godot(private val context: Context) {
GodotPluginRegistry.getPluginRegistry()
}
- private val accelerometer_enabled = AtomicBoolean(false)
+ private val accelerometerEnabled = AtomicBoolean(false)
private val mAccelerometer: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
}
- private val gravity_enabled = AtomicBoolean(false)
+ private val gravityEnabled = AtomicBoolean(false)
private val mGravity: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
}
- private val magnetometer_enabled = AtomicBoolean(false)
+ private val magnetometerEnabled = AtomicBoolean(false)
private val mMagnetometer: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
}
- private val gyroscope_enabled = AtomicBoolean(false)
+ private val gyroscopeEnabled = AtomicBoolean(false)
private val mGyroscope: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
}
- private val uiChangeListener = View.OnSystemUiVisibilityChangeListener { visibility: Int ->
- if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) {
- val decorView = requireActivity().window.decorView
- decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
- View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
- }}
-
val tts = GodotTTS(context)
val directoryAccessHandler = DirectoryAccessHandler(context)
val fileAccessHandler = FileAccessHandler(context)
@@ -185,7 +178,7 @@ class Godot(private val context: Context) {
private var xrMode = XRMode.REGULAR
private var expansionPackPath: String = ""
private var useApkExpansion = false
- private var useImmersive = false
+ private val useImmersive = AtomicBoolean(false)
private var useDebugOpengl = false
private var darkMode = false
@@ -254,15 +247,9 @@ class Godot(private val context: Context) {
xrMode = XRMode.OPENXR
} else if (commandLine[i] == "--debug_opengl") {
useDebugOpengl = true
- } else if (commandLine[i] == "--use_immersive") {
- useImmersive = true
- window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or // hide nav bar
- View.SYSTEM_UI_FLAG_FULLSCREEN or // hide status bar
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
- registerUiChangeListener()
+ } else if (commandLine[i] == "--fullscreen") {
+ useImmersive.set(true)
+ newArgs.add(commandLine[i])
} else if (commandLine[i] == "--use_apk_expansion") {
useApkExpansion = true
} else if (hasExtra && commandLine[i] == "--apk_expansion_md5") {
@@ -336,6 +323,54 @@ class Godot(private val context: Context) {
}
/**
+ * Toggle immersive mode.
+ * Must be called from the UI thread.
+ */
+ private fun enableImmersiveMode(enabled: Boolean, override: Boolean = false) {
+ val activity = getActivity() ?: return
+ val window = activity.window ?: return
+
+ if (!useImmersive.compareAndSet(!enabled, enabled) && !override) {
+ return
+ }
+
+ WindowCompat.setDecorFitsSystemWindows(window, !enabled)
+ val controller = WindowInsetsControllerCompat(window, window.decorView)
+ if (enabled) {
+ controller.hide(WindowInsetsCompat.Type.systemBars())
+ controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ } else {
+ val fullScreenThemeValue = TypedValue()
+ val hasStatusBar = if (activity.theme.resolveAttribute(android.R.attr.windowFullscreen, fullScreenThemeValue, true) && fullScreenThemeValue.type == TypedValue.TYPE_INT_BOOLEAN) {
+ fullScreenThemeValue.data == 0
+ } else {
+ // Fallback to checking the editor build
+ !isEditorBuild()
+ }
+
+ val types = if (hasStatusBar) {
+ WindowInsetsCompat.Type.navigationBars() or WindowInsetsCompat.Type.statusBars()
+ } else {
+ WindowInsetsCompat.Type.navigationBars()
+ }
+ controller.show(types)
+ }
+ }
+
+ /**
+ * Invoked from the render thread to toggle the immersive mode.
+ */
+ @Keep
+ private fun nativeEnableImmersiveMode(enabled: Boolean) {
+ runOnUiThread {
+ enableImmersiveMode(enabled)
+ }
+ }
+
+ @Keep
+ fun isInImmersiveMode() = useImmersive.get()
+
+ /**
* Initializes the native layer of the Godot engine.
*
* This must be preceded by [onCreate] and followed by [onInitRenderView] to complete
@@ -552,15 +587,7 @@ class Godot(private val context: Context) {
renderView?.onActivityResumed()
registerSensorsIfNeeded()
- if (useImmersive) {
- val window = requireActivity().window
- window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or // hide nav bar
- View.SYSTEM_UI_FLAG_FULLSCREEN or // hide status bar
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
- }
+ enableImmersiveMode(useImmersive.get(), true)
for (plugin in pluginRegistry.allPlugins) {
plugin.onMainResume()
}
@@ -571,16 +598,16 @@ class Godot(private val context: Context) {
return
}
- if (accelerometer_enabled.get() && mAccelerometer != null) {
+ if (accelerometerEnabled.get() && mAccelerometer != null) {
mSensorManager.registerListener(godotInputHandler, mAccelerometer, SensorManager.SENSOR_DELAY_GAME)
}
- if (gravity_enabled.get() && mGravity != null) {
+ if (gravityEnabled.get() && mGravity != null) {
mSensorManager.registerListener(godotInputHandler, mGravity, SensorManager.SENSOR_DELAY_GAME)
}
- if (magnetometer_enabled.get() && mMagnetometer != null) {
+ if (magnetometerEnabled.get() && mMagnetometer != null) {
mSensorManager.registerListener(godotInputHandler, mMagnetometer, SensorManager.SENSOR_DELAY_GAME)
}
- if (gyroscope_enabled.get() && mGyroscope != null) {
+ if (gyroscopeEnabled.get() && mGyroscope != null) {
mSensorManager.registerListener(godotInputHandler, mGyroscope, SensorManager.SENSOR_DELAY_GAME)
}
}
@@ -696,10 +723,10 @@ class Godot(private val context: Context) {
Log.v(TAG, "OnGodotMainLoopStarted")
godotMainLoopStarted.set(true)
- accelerometer_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_accelerometer")))
- gravity_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gravity")))
- gyroscope_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gyroscope")))
- magnetometer_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer")))
+ accelerometerEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_accelerometer")))
+ gravityEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gravity")))
+ gyroscopeEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gyroscope")))
+ magnetometerEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer")))
runOnUiThread {
registerSensorsIfNeeded()
@@ -724,11 +751,6 @@ class Godot(private val context: Context) {
primaryHost?.onGodotRestartRequested(this)
}
- private fun registerUiChangeListener() {
- val decorView = requireActivity().window.decorView
- decorView.setOnSystemUiVisibilityChangeListener(uiChangeListener)
- }
-
fun alert(
@StringRes messageResId: Int,
@StringRes titleResId: Int,
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
index 913e3d04c5..474c6e9b2f 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
@@ -53,8 +53,6 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
private val TAG = GodotActivity::class.java.simpleName
@JvmStatic
- protected val EXTRA_FORCE_QUIT = "force_quit_requested"
- @JvmStatic
protected val EXTRA_NEW_LAUNCH = "new_launch_requested"
}
@@ -128,12 +126,6 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
}
private fun handleStartIntent(intent: Intent, newLaunch: Boolean) {
- val forceQuitRequested = intent.getBooleanExtra(EXTRA_FORCE_QUIT, false)
- if (forceQuitRequested) {
- Log.d(TAG, "Force quit requested, terminating..")
- ProcessPhoenix.forceQuit(this)
- return
- }
if (!newLaunch) {
val newLaunchRequested = intent.getBooleanExtra(EXTRA_NEW_LAUNCH, false)
if (newLaunchRequested) {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java b/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java
index b1bce45fbb..d9afdf90b1 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java
@@ -24,6 +24,7 @@ package org.godotengine.godot.utils;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -44,6 +45,9 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
*/
public final class ProcessPhoenix extends Activity {
private static final String KEY_RESTART_INTENTS = "phoenix_restart_intents";
+ // -- GODOT start --
+ private static final String KEY_RESTART_ACTIVITY_OPTIONS = "phoenix_restart_activity_options";
+ // -- GODOT end --
private static final String KEY_MAIN_PROCESS_PID = "phoenix_main_process_pid";
/**
@@ -56,12 +60,23 @@ public final class ProcessPhoenix extends Activity {
triggerRebirth(context, getRestartIntent(context));
}
+ // -- GODOT start --
/**
* Call to restart the application process using the specified intents.
* <p>
* Behavior of the current process after invoking this method is undefined.
*/
public static void triggerRebirth(Context context, Intent... nextIntents) {
+ triggerRebirth(context, null, nextIntents);
+ }
+
+ /**
+ * Call to restart the application process using the specified intents launched with the given
+ * {@link ActivityOptions}.
+ * <p>
+ * Behavior of the current process after invoking this method is undefined.
+ */
+ public static void triggerRebirth(Context context, Bundle activityOptions, Intent... nextIntents) {
if (nextIntents.length < 1) {
throw new IllegalArgumentException("intents cannot be empty");
}
@@ -72,10 +87,12 @@ public final class ProcessPhoenix extends Activity {
intent.addFlags(FLAG_ACTIVITY_NEW_TASK); // In case we are called with non-Activity context.
intent.putParcelableArrayListExtra(KEY_RESTART_INTENTS, new ArrayList<>(Arrays.asList(nextIntents)));
intent.putExtra(KEY_MAIN_PROCESS_PID, Process.myPid());
+ if (activityOptions != null) {
+ intent.putExtra(KEY_RESTART_ACTIVITY_OPTIONS, activityOptions);
+ }
context.startActivity(intent);
}
- // -- GODOT start --
/**
* Finish the activity and kill its process
*/
@@ -112,9 +129,11 @@ public final class ProcessPhoenix extends Activity {
super.onCreate(savedInstanceState);
// -- GODOT start --
- ArrayList<Intent> intents = getIntent().getParcelableArrayListExtra(KEY_RESTART_INTENTS);
- startActivities(intents.toArray(new Intent[intents.size()]));
- forceQuit(this, getIntent().getIntExtra(KEY_MAIN_PROCESS_PID, -1));
+ Intent launchIntent = getIntent();
+ ArrayList<Intent> intents = launchIntent.getParcelableArrayListExtra(KEY_RESTART_INTENTS);
+ Bundle activityOptions = launchIntent.getBundleExtra(KEY_RESTART_ACTIVITY_OPTIONS);
+ startActivities(intents.toArray(new Intent[intents.size()]), activityOptions);
+ forceQuit(this, launchIntent.getIntExtra(KEY_MAIN_PROCESS_PID, -1));
// -- GODOT end --
}
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 1114969de8..390677df22 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -51,7 +51,10 @@
#include "core/config/project_settings.h"
#include "core/input/input.h"
#include "main/main.h"
+
+#ifndef _3D_DISABLED
#include "servers/xr_server.h"
+#endif // _3D_DISABLED
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
@@ -271,14 +274,16 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env,
}
if (step.get() == STEP_SHOW_LOGO) {
- bool xr_enabled;
+ bool xr_enabled = false;
+#ifndef _3D_DISABLED
+ // Unlike PCVR, there's no additional 2D screen onto which to render the boot logo,
+ // so we skip this step if xr is enabled.
if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) {
xr_enabled = GLOBAL_GET("xr/shaders/enabled");
} else {
xr_enabled = XRServer::get_xr_mode() == XRServer::XRMODE_ON;
}
- // Unlike PCVR, there's no additional 2D screen onto which to render the boot logo,
- // so we skip this step if xr is enabled.
+#endif // _3D_DISABLED
if (!xr_enabled) {
Main::setup_boot_logo();
}
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index f1759af54a..d3b30e4589 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -86,6 +86,8 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_has_feature = p_env->GetMethodID(godot_class, "hasFeature", "(Ljava/lang/String;)Z");
_sign_apk = p_env->GetMethodID(godot_class, "nativeSignApk", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I");
_verify_apk = p_env->GetMethodID(godot_class, "nativeVerifyApk", "(Ljava/lang/String;)I");
+ _enable_immersive_mode = p_env->GetMethodID(godot_class, "nativeEnableImmersiveMode", "(Z)V");
+ _is_in_immersive_mode = p_env->GetMethodID(godot_class, "isInImmersiveMode", "()Z");
}
GodotJavaWrapper::~GodotJavaWrapper() {
@@ -465,3 +467,21 @@ Error GodotJavaWrapper::verify_apk(const String &p_apk_path) {
return ERR_UNCONFIGURED;
}
}
+
+void GodotJavaWrapper::enable_immersive_mode(bool p_enabled) {
+ if (_enable_immersive_mode) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL(env);
+ env->CallVoidMethod(godot_instance, _enable_immersive_mode, p_enabled);
+ }
+}
+
+bool GodotJavaWrapper::is_in_immersive_mode() {
+ if (_is_in_immersive_mode) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, false);
+ return env->CallBooleanMethod(godot_instance, _is_in_immersive_mode);
+ } else {
+ return false;
+ }
+}
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index 6b66565981..51d7f98541 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -77,6 +77,8 @@ private:
jmethodID _has_feature = nullptr;
jmethodID _sign_apk = nullptr;
jmethodID _verify_apk = nullptr;
+ jmethodID _enable_immersive_mode = nullptr;
+ jmethodID _is_in_immersive_mode = nullptr;
public:
GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance);
@@ -122,6 +124,9 @@ public:
// Sign and verify apks
Error sign_apk(const String &p_input_path, const String &p_output_path, const String &p_keystore_path, const String &p_keystore_user, const String &p_keystore_password);
Error verify_apk(const String &p_apk_path);
+
+ void enable_immersive_mode(bool p_enabled);
+ bool is_in_immersive_mode();
};
#endif // JAVA_GODOT_WRAPPER_H
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index e4b5392c4e..b99e825540 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -2017,11 +2017,11 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset>
return OK;
}
-Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
return _export_project_helper(p_preset, p_debug, p_path, p_flags, false, false);
}
-Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags, bool p_simulator, bool p_oneclick) {
+Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, bool p_simulator, bool p_oneclick) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
const String dest_dir = p_path.get_base_dir() + "/";
@@ -2983,7 +2983,7 @@ void EditorExportPlatformIOS::_update_preset_status() {
}
#endif
-Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
+Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
#ifdef MACOS_ENABLED
ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER);
@@ -3029,11 +3029,11 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int
String host = EDITOR_GET("network/debug/remote_host");
int remote_port = (int)EDITOR_GET("network/debug/remote_port");
- if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST)) {
host = "localhost";
}
- if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) {
int port = EDITOR_GET("filesystem/file_server/port");
String passwd = EDITOR_GET("filesystem/file_server/password");
cmd_args_list.push_back("--remote-fs");
@@ -3044,7 +3044,7 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int
}
}
- if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) {
cmd_args_list.push_back("--remote-debug");
cmd_args_list.push_back(get_debug_protocol() + host + ":" + String::num(remote_port));
@@ -3066,11 +3066,11 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int
}
}
- if (p_debug_flags & DEBUG_FLAG_VIEW_COLLISIONS) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_COLLISIONS)) {
cmd_args_list.push_back("--debug-collisions");
}
- if (p_debug_flags & DEBUG_FLAG_VIEW_NAVIGATION) {
+ if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_NAVIGATION)) {
cmd_args_list.push_back("--debug-navigation");
}
diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h
index 1964906c27..db7c0553dd 100644
--- a/platform/ios/export/export_plugin.h
+++ b/platform/ios/export/export_plugin.h
@@ -146,7 +146,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
Error _export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets);
Error _export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug);
- Error _export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags, bool p_simulator, bool p_oneclick);
+ Error _export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, bool p_simulator, bool p_oneclick);
bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const;
@@ -169,7 +169,7 @@ public:
virtual Ref<ImageTexture> get_option_icon(int p_index) const override;
virtual String get_option_label(int p_index) const override;
virtual String get_option_tooltip(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual bool poll_export() override {
bool dc = devices_changed.is_set();
@@ -202,7 +202,7 @@ public:
return list;
}
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp
index 0032b898d2..69ba742f72 100644
--- a/platform/linuxbsd/export/export_plugin.cpp
+++ b/platform/linuxbsd/export/export_plugin.cpp
@@ -60,7 +60,7 @@ Error EditorExportPlatformLinuxBSD::_export_debug_script(const Ref<EditorExportP
return OK;
}
-Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
String custom_debug = p_preset->get("custom_template/debug");
String custom_release = p_preset->get("custom_template/release");
String arch = p_preset->get("binary_format/architecture");
@@ -458,7 +458,7 @@ void EditorExportPlatformLinuxBSD::cleanup() {
cleanup_commands.clear();
}
-Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
+Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
cleanup();
if (p_device) { // Stop command, cleanup only.
return OK;
@@ -512,8 +512,7 @@ Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset,
String cmd_args;
{
- Vector<String> cmd_args_list;
- gen_debug_flags(cmd_args_list, p_debug_flags);
+ Vector<String> cmd_args_list = gen_export_flags(p_debug_flags);
for (int i = 0; i < cmd_args_list.size(); i++) {
if (i != 0) {
cmd_args += " ";
@@ -522,7 +521,7 @@ Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset,
}
}
- const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT);
+ const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT);
int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port");
print_line("Creating temporary directory...");
diff --git a/platform/linuxbsd/export/export_plugin.h b/platform/linuxbsd/export/export_plugin.h
index bbc55b82ce..1d9ef01d1a 100644
--- a/platform/linuxbsd/export/export_plugin.h
+++ b/platform/linuxbsd/export/export_plugin.h
@@ -76,7 +76,7 @@ public:
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override;
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
virtual String get_template_file_name(const String &p_target, const String &p_arch) const override;
virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) override;
virtual bool is_executable(const String &p_path) const override;
@@ -87,7 +87,7 @@ public:
virtual int get_options_count() const override;
virtual String get_option_label(int p_index) const override;
virtual String get_option_tooltip(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual void cleanup() override;
EditorExportPlatformLinuxBSD();
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp
index 671da7fc2a..94a748e414 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.cpp
+++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp
@@ -210,7 +210,15 @@ void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter,
append_dbus_string(&struct_iter, p_filter_names[i]);
dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter);
- const String &flt = p_filter_exts[i];
+ const String &flt_orig = p_filter_exts[i];
+ String flt;
+ for (int j = 0; j < flt_orig.length(); j++) {
+ if (is_unicode_letter(flt_orig[j])) {
+ flt += vformat("[%c%c]", String::char_lowercase(flt_orig[j]), String::char_uppercase(flt_orig[j]));
+ } else {
+ flt += flt_orig[j];
+ }
+ }
int filter_slice_count = flt.get_slice_count(",");
for (int j = 0; j < filter_slice_count; j++) {
dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);
@@ -377,17 +385,26 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
String flt = tokens[0].strip_edges();
if (!flt.is_empty()) {
if (tokens.size() == 2) {
- filter_exts.push_back(flt);
+ if (flt == "*.*") {
+ filter_exts.push_back("*");
+ } else {
+ filter_exts.push_back(flt);
+ }
filter_names.push_back(tokens[1]);
} else {
- filter_exts.push_back(flt);
- filter_names.push_back(flt);
+ if (flt == "*.*") {
+ filter_exts.push_back("*");
+ filter_names.push_back(RTR("All Files"));
+ } else {
+ filter_exts.push_back(flt);
+ filter_names.push_back(flt);
+ }
}
}
}
}
if (filter_names.is_empty()) {
- filter_exts.push_back("*.*");
+ filter_exts.push_back("*");
filter_names.push_back(RTR("All Files"));
}
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index 290b0082fc..8372600ae9 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -1505,7 +1505,7 @@ Error EditorExportPlatformMacOS::_export_debug_script(const Ref<EditorExportPres
return OK;
}
-Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
const String base_dir = p_path.get_base_dir();
@@ -2511,7 +2511,7 @@ void EditorExportPlatformMacOS::cleanup() {
cleanup_commands.clear();
}
-Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
+Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
cleanup();
if (p_device) { // Stop command, cleanup only.
return OK;
@@ -2573,8 +2573,7 @@ Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, in
String cmd_args;
{
- Vector<String> cmd_args_list;
- gen_debug_flags(cmd_args_list, p_debug_flags);
+ Vector<String> cmd_args_list = gen_export_flags(p_debug_flags);
for (int i = 0; i < cmd_args_list.size(); i++) {
if (i != 0) {
cmd_args += " ";
@@ -2583,7 +2582,7 @@ Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, in
}
}
- const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT);
+ const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT);
int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port");
print_line("Creating temporary directory...");
diff --git a/platform/macos/export/export_plugin.h b/platform/macos/export/export_plugin.h
index 062a2e5f95..5457c687d3 100644
--- a/platform/macos/export/export_plugin.h
+++ b/platform/macos/export/export_plugin.h
@@ -147,7 +147,7 @@ public:
virtual bool is_executable(const String &p_path) const override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
@@ -167,7 +167,7 @@ public:
virtual int get_options_count() const override;
virtual String get_option_label(int p_index) const override;
virtual String get_option_tooltip(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual void cleanup() override;
EditorExportPlatformMacOS();
diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp
index d8c1b6033d..5faab74d7b 100644
--- a/platform/web/export/export_plugin.cpp
+++ b/platform/web/export/export_plugin.cpp
@@ -130,15 +130,14 @@ void EditorExportPlatformWeb::_replace_strings(const HashMap<String, String> &p_
}
}
-void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) {
+void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, BitField<EditorExportPlatform::DebugFlags> p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) {
// Engine.js config
Dictionary config;
Array libs;
for (int i = 0; i < p_shared_objects.size(); i++) {
libs.push_back(p_shared_objects[i].path.get_file());
}
- Vector<String> flags;
- gen_export_flags(flags, p_flags & (~DEBUG_FLAG_DUMB_CLIENT));
+ Vector<String> flags = gen_export_flags(p_flags & (~DEBUG_FLAG_DUMB_CLIENT));
Array args;
for (int i = 0; i < flags.size(); i++) {
args.push_back(flags[i]);
@@ -450,7 +449,7 @@ List<String> EditorExportPlatformWeb::get_binary_extensions(const Ref<EditorExpo
return list;
}
-Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
const String custom_debug = p_preset->get("custom_template/debug");
@@ -744,7 +743,7 @@ String EditorExportPlatformWeb::get_option_tooltip(int p_index) const {
return "";
}
-Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) {
+Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
const uint16_t bind_port = EDITOR_GET("export/web/http_port");
// Resolve host if needed.
const String bind_host = EDITOR_GET("export/web/http_host");
diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h
index 2f67d8107f..3c743e2e74 100644
--- a/platform/web/export/export_plugin.h
+++ b/platform/web/export/export_plugin.h
@@ -98,7 +98,7 @@ class EditorExportPlatformWeb : public EditorExportPlatform {
Error _extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa);
void _replace_strings(const HashMap<String, String> &p_replaces, Vector<uint8_t> &r_template);
- void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes);
+ void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, BitField<EditorExportPlatform::DebugFlags> p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes);
Error _add_manifest_icon(const String &p_path, const String &p_icon, int p_size, Array &r_arr);
Error _build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects);
Error _write_or_error(const uint8_t *p_content, int p_len, String p_path);
@@ -120,14 +120,14 @@ public:
virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override;
virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
virtual bool poll_export() override;
virtual int get_options_count() const override;
virtual String get_option_label(int p_index) const override;
virtual String get_option_tooltip(int p_index) const override;
virtual Ref<ImageTexture> get_option_icon(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual Ref<Texture2D> get_run_icon() const override;
virtual void get_platform_features(List<String> *r_features) const override {
diff --git a/platform/windows/console_wrapper_windows.cpp b/platform/windows/console_wrapper_windows.cpp
index 133711a9ea..1ba09b236b 100644
--- a/platform/windows/console_wrapper_windows.cpp
+++ b/platform/windows/console_wrapper_windows.cpp
@@ -40,8 +40,8 @@
int main(int argc, char *argv[]) {
// Get executable name.
- WCHAR exe_name[MAX_PATH] = {};
- if (!GetModuleFileNameW(nullptr, exe_name, MAX_PATH)) {
+ WCHAR exe_name[32767] = {};
+ if (!GetModuleFileNameW(nullptr, exe_name, 32767)) {
wprintf(L"GetModuleFileName failed, error %d\n", GetLastError());
return -1;
}
diff --git a/platform/windows/crash_handler_windows_seh.cpp b/platform/windows/crash_handler_windows_seh.cpp
index 2abe285d31..a6015092e8 100644
--- a/platform/windows/crash_handler_windows_seh.cpp
+++ b/platform/windows/crash_handler_windows_seh.cpp
@@ -118,7 +118,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
HANDLE process = GetCurrentProcess();
HANDLE hThread = GetCurrentThread();
DWORD offset_from_symbol = 0;
- IMAGEHLP_LINE64 line = { 0 };
+ IMAGEHLP_LINE64 line = {};
std::vector<module_data> modules;
DWORD cbNeeded;
std::vector<HMODULE> module_handles(1);
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 11dd4548f1..92ac921cee 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -13,40 +13,33 @@ if TYPE_CHECKING:
# To match other platforms
STACK_SIZE = 8388608
+STACK_SIZE_SANITIZERS = 30 * 1024 * 1024
def get_name():
return "Windows"
-def try_cmd(test, prefix, arch):
+def try_cmd(test, prefix, arch, check_clang=False):
+ archs = ["x86_64", "x86_32", "arm64", "arm32"]
if arch:
+ archs = [arch]
+
+ for a in archs:
try:
out = subprocess.Popen(
- get_mingw_bin_prefix(prefix, arch) + test,
+ get_mingw_bin_prefix(prefix, a) + test,
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
)
- out.communicate()
+ outs, errs = out.communicate()
if out.returncode == 0:
+ if check_clang and not outs.startswith(b"clang"):
+ return False
return True
except Exception:
pass
- else:
- for a in ["x86_64", "x86_32", "arm64", "arm32"]:
- try:
- out = subprocess.Popen(
- get_mingw_bin_prefix(prefix, a) + test,
- shell=True,
- stderr=subprocess.PIPE,
- stdout=subprocess.PIPE,
- )
- out.communicate()
- if out.returncode == 0:
- return True
- except Exception:
- pass
return False
@@ -203,6 +196,7 @@ def get_opts():
BoolVariable("use_llvm", "Use the LLVM compiler", False),
BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True),
BoolVariable("use_asan", "Use address sanitizer (ASAN)", False),
+ BoolVariable("use_ubsan", "Use LLVM compiler undefined behavior sanitizer (UBSAN)", False),
BoolVariable("debug_crt", "Compile with MSVC's debug CRT (/MDd)", False),
BoolVariable("incremental_link", "Use MSVC incremental linking. May increase or decrease build times.", False),
BoolVariable("silence_msvc", "Silence MSVC's cl/link stdout bloat, redirecting any errors to stderr.", True),
@@ -387,6 +381,15 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
## Compile/link flags
+ if env["use_llvm"]:
+ env["CC"] = "clang-cl"
+ env["CXX"] = "clang-cl"
+ env["LINK"] = "lld-link"
+ env["AR"] = "llvm-lib"
+
+ env.AppendUnique(CPPDEFINES=["R128_STDC_ONLY"])
+ env.extra_suffix = ".llvm" + env.extra_suffix
+
env["MAXLINELENGTH"] = 8192 # Windows Vista and beyond, so always applicable.
if env["silence_msvc"] and not env.GetOption("clean"):
@@ -471,7 +474,6 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
env.AppendUnique(CCFLAGS=["/Gd", "/GR", "/nologo"])
env.AppendUnique(CCFLAGS=["/utf-8"]) # Force to use Unicode encoding.
- env.AppendUnique(CXXFLAGS=["/TP"]) # assume all sources are C++
# Once it was thought that only debug builds would be too large,
# but this has recently stopped being true. See the mingw function
# for notes on why this shouldn't be enabled for gcc
@@ -507,6 +509,7 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
if env["use_asan"]:
env.extra_suffix += ".san"
prebuilt_lib_extra_suffix = ".san"
+ env.AppendUnique(CPPDEFINES=["SANITIZERS_ENABLED"])
env.Append(CCFLAGS=["/fsanitize=address"])
env.Append(LINKFLAGS=["/INFERASANLIBS"])
@@ -595,6 +598,9 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
if env["target"] in ["editor", "template_debug"]:
LIBS += ["psapi", "dbghelp"]
+ if env["use_llvm"]:
+ LIBS += [f"clang_rt.builtins-{env['arch']}"]
+
env.Append(LINKFLAGS=[p + env["LIBSUFFIX"] for p in LIBS])
if vcvars_msvc_config:
@@ -610,14 +616,22 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
if env["lto"] != "none":
if env["lto"] == "thin":
- print_error("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.")
- sys.exit(255)
- env.AppendUnique(CCFLAGS=["/GL"])
- env.AppendUnique(ARFLAGS=["/LTCG"])
- if env["progress"]:
- env.AppendUnique(LINKFLAGS=["/LTCG:STATUS"])
+ if not env["use_llvm"]:
+ print("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.")
+ sys.exit(255)
+
+ env.Append(CCFLAGS=["-flto=thin"])
+ env.Append(LINKFLAGS=["-flto=thin"])
+ elif env["use_llvm"]:
+ env.Append(CCFLAGS=["-flto"])
+ env.Append(LINKFLAGS=["-flto"])
else:
- env.AppendUnique(LINKFLAGS=["/LTCG"])
+ env.AppendUnique(CCFLAGS=["/GL"])
+ env.AppendUnique(ARFLAGS=["/LTCG"])
+ if env["progress"]:
+ env.AppendUnique(LINKFLAGS=["/LTCG:STATUS"])
+ else:
+ env.AppendUnique(LINKFLAGS=["/LTCG"])
if vcvars_msvc_config:
env.Prepend(CPPPATH=[p for p in str(os.getenv("INCLUDE")).split(";")])
@@ -628,7 +642,66 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config):
env["BUILDERS"]["Program"] = methods.precious_program
env.Append(LINKFLAGS=["/NATVIS:platform\\windows\\godot.natvis"])
- env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE)])
+
+ if env["use_asan"]:
+ env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE_SANITIZERS)])
+ else:
+ env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE)])
+
+
+def get_ar_version(env):
+ ret = {
+ "major": -1,
+ "minor": -1,
+ "patch": -1,
+ "is_llvm": False,
+ }
+ try:
+ output = (
+ subprocess.check_output([env.subst(env["AR"]), "--version"], shell=(os.name == "nt"))
+ .strip()
+ .decode("utf-8")
+ )
+ except (subprocess.CalledProcessError, OSError):
+ print_warning("Couldn't check version of `ar`.")
+ return ret
+
+ match = re.search(r"GNU ar \(GNU Binutils\) (\d+)\.(\d+)(:?\.(\d+))?", output)
+ if match:
+ ret["major"] = int(match[1])
+ ret["minor"] = int(match[2])
+ if match[3]:
+ ret["patch"] = int(match[3])
+ else:
+ ret["patch"] = 0
+ return ret
+
+ match = re.search(r"LLVM version (\d+)\.(\d+)\.(\d+)", output)
+ if match:
+ ret["major"] = int(match[1])
+ ret["minor"] = int(match[2])
+ ret["patch"] = int(match[3])
+ ret["is_llvm"] = True
+ return ret
+
+ print_warning("Couldn't parse version of `ar`.")
+ return ret
+
+
+def get_is_ar_thin_supported(env):
+ """Check whether `ar --thin` is supported. It is only supported since Binutils 2.38 or LLVM 14."""
+ ar_version = get_ar_version(env)
+ if ar_version["major"] == -1:
+ return False
+
+ if ar_version["is_llvm"]:
+ return ar_version["major"] >= 14
+
+ if ar_version["major"] == 2:
+ return ar_version["minor"] >= 38
+
+ print_warning("Unknown Binutils `ar` version.")
+ return False
def configure_mingw(env: "SConsEnvironment"):
@@ -644,6 +717,10 @@ def configure_mingw(env: "SConsEnvironment"):
if env["use_llvm"] and not try_cmd("clang --version", env["mingw_prefix"], env["arch"]):
env["use_llvm"] = False
+ if not env["use_llvm"] and try_cmd("gcc --version", env["mingw_prefix"], env["arch"], True):
+ print("Detected GCC to be a wrapper for Clang.")
+ env["use_llvm"] = True
+
# TODO: Re-evaluate the need for this / streamline with common config.
if env["target"] == "template_release":
if env["arch"] != "arm64":
@@ -721,7 +798,10 @@ def configure_mingw(env: "SConsEnvironment"):
env.Append(CCFLAGS=["-flto"])
env.Append(LINKFLAGS=["-flto"])
- env.Append(LINKFLAGS=["-Wl,--stack," + str(STACK_SIZE)])
+ if env["use_asan"]:
+ env.Append(LINKFLAGS=["-Wl,--stack," + str(STACK_SIZE_SANITIZERS)])
+ else:
+ env.Append(LINKFLAGS=["-Wl,--stack," + str(STACK_SIZE)])
## Compile flags
@@ -732,6 +812,33 @@ def configure_mingw(env: "SConsEnvironment"):
if not env["use_llvm"]:
env.Append(CCFLAGS=["-mwindows"])
+ if env["use_asan"] or env["use_ubsan"]:
+ if not env["use_llvm"]:
+ print("GCC does not support sanitizers on Windows.")
+ sys.exit(255)
+ if env["arch"] not in ["x86_32", "x86_64"]:
+ print("Sanitizers are only supported for x86_32 and x86_64.")
+ sys.exit(255)
+
+ env.extra_suffix += ".san"
+ env.AppendUnique(CPPDEFINES=["SANITIZERS_ENABLED"])
+ san_flags = []
+ if env["use_asan"]:
+ san_flags.append("-fsanitize=address")
+ if env["use_ubsan"]:
+ san_flags.append("-fsanitize=undefined")
+ # Disable the vptr check since it gets triggered on any COM interface calls.
+ san_flags.append("-fno-sanitize=vptr")
+ env.Append(CFLAGS=san_flags)
+ env.Append(CCFLAGS=san_flags)
+ env.Append(LINKFLAGS=san_flags)
+
+ if env["use_llvm"] and os.name == "nt" and methods._colorize:
+ env.Append(CCFLAGS=["$(-fansi-escape-codes$)", "$(-fcolor-diagnostics$)"])
+
+ if get_is_ar_thin_supported(env):
+ env.Append(ARFLAGS=["--thin"])
+
env.Append(CPPDEFINES=["WINDOWS_ENABLED", "WASAPI_ENABLED", "WINMIDI_ENABLED"])
env.Append(
CPPDEFINES=[
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 6fa3f2c9d6..8bcd556c22 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -133,9 +133,17 @@ String DisplayServerWindows::get_name() const {
}
void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) {
+ if (p_mode == MOUSE_MODE_HIDDEN || p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED_HIDDEN) {
+ // Hide cursor before moving.
+ if (hCursor == nullptr) {
+ hCursor = SetCursor(nullptr);
+ } else {
+ SetCursor(nullptr);
+ }
+ }
+
if (windows.has(MAIN_WINDOW_ID) && (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED || p_mode == MOUSE_MODE_CONFINED_HIDDEN)) {
// Mouse is grabbed (captured or confined).
-
WindowID window_id = _get_focused_window_or_popup();
if (!windows.has(window_id)) {
window_id = MAIN_WINDOW_ID;
@@ -165,13 +173,8 @@ void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) {
_register_raw_input_devices(INVALID_WINDOW_ID);
}
- if (p_mode == MOUSE_MODE_HIDDEN || p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED_HIDDEN) {
- if (hCursor == nullptr) {
- hCursor = SetCursor(nullptr);
- } else {
- SetCursor(nullptr);
- }
- } else {
+ if (p_mode == MOUSE_MODE_VISIBLE || p_mode == MOUSE_MODE_CONFINED) {
+ // Show cursor.
CursorShape c = cursor_shape;
cursor_shape = CURSOR_MAX;
cursor_set_shape(c);
@@ -314,7 +317,7 @@ public:
if (!lpw_path) {
return S_FALSE;
}
- String path = String::utf16((const char16_t *)lpw_path).simplify_path();
+ String path = String::utf16((const char16_t *)lpw_path).replace("\\", "/").trim_prefix(R"(\\?\)").simplify_path();
if (!path.begins_with(root.simplify_path())) {
return S_FALSE;
}
@@ -539,7 +542,26 @@ void DisplayServerWindows::_thread_fd_monitor(void *p_ud) {
pfd->SetOptions(flags | FOS_FORCEFILESYSTEM);
pfd->SetTitle((LPCWSTR)fd->title.utf16().ptr());
- String dir = fd->current_directory.replace("/", "\\");
+ String dir = ProjectSettings::get_singleton()->globalize_path(fd->current_directory);
+ if (dir == ".") {
+ dir = OS::get_singleton()->get_executable_path().get_base_dir();
+ }
+ if (dir.is_relative_path() || dir == ".") {
+ Char16String current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());
+ if (dir == ".") {
+ dir = String::utf16((const char16_t *)current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace("\\", "/");
+ } else {
+ dir = String::utf16((const char16_t *)current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace("\\", "/").path_join(dir);
+ }
+ }
+ dir = dir.simplify_path();
+ dir = dir.replace("/", "\\");
+ if (!dir.is_network_share_path() && !dir.begins_with(R"(\\?\)")) {
+ dir = R"(\\?\)" + dir;
+ }
IShellItem *shellitem = nullptr;
hr = SHCreateItemFromParsingName((LPCWSTR)dir.utf16().ptr(), nullptr, IID_IShellItem, (void **)&shellitem);
@@ -582,7 +604,7 @@ void DisplayServerWindows::_thread_fd_monitor(void *p_ud) {
PWSTR file_path = nullptr;
hr = result->GetDisplayName(SIGDN_FILESYSPATH, &file_path);
if (SUCCEEDED(hr)) {
- file_names.push_back(String::utf16((const char16_t *)file_path));
+ file_names.push_back(String::utf16((const char16_t *)file_path).replace("\\", "/").trim_prefix(R"(\\?\)"));
CoTaskMemFree(file_path);
}
result->Release();
@@ -596,7 +618,7 @@ void DisplayServerWindows::_thread_fd_monitor(void *p_ud) {
PWSTR file_path = nullptr;
hr = result->GetDisplayName(SIGDN_FILESYSPATH, &file_path);
if (SUCCEEDED(hr)) {
- file_names.push_back(String::utf16((const char16_t *)file_path));
+ file_names.push_back(String::utf16((const char16_t *)file_path).replace("\\", "/").trim_prefix(R"(\\?\)"));
CoTaskMemFree(file_path);
}
result->Release();
diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp
index b465bd4ecd..8d3f4bb269 100644
--- a/platform/windows/export/export_plugin.cpp
+++ b/platform/windows/export/export_plugin.cpp
@@ -167,7 +167,7 @@ Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPres
}
}
-Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
if (p_preset->get("application/modify_resources")) {
_rcedit_add_data(p_preset, p_path, false);
String wrapper_path = p_path.get_basename() + ".console.exe";
@@ -178,7 +178,7 @@ Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset>
return OK;
}
-Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
String custom_debug = p_preset->get("custom_template/debug");
String custom_release = p_preset->get("custom_template/release");
String arch = p_preset->get("binary_format/architecture");
@@ -996,7 +996,7 @@ void EditorExportPlatformWindows::cleanup() {
cleanup_commands.clear();
}
-Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) {
+Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) {
cleanup();
if (p_device) { // Stop command, cleanup only.
return OK;
@@ -1050,8 +1050,7 @@ Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset,
String cmd_args;
{
- Vector<String> cmd_args_list;
- gen_debug_flags(cmd_args_list, p_debug_flags);
+ Vector<String> cmd_args_list = gen_export_flags(p_debug_flags);
for (int i = 0; i < cmd_args_list.size(); i++) {
if (i != 0) {
cmd_args += " ";
@@ -1060,7 +1059,7 @@ Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset,
}
}
- const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT);
+ const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT);
int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port");
print_line("Creating temporary directory...");
diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h
index 6ccb4a15a7..e86aac83d4 100644
--- a/platform/windows/export/export_plugin.h
+++ b/platform/windows/export/export_plugin.h
@@ -76,8 +76,8 @@ class EditorExportPlatformWindows : public EditorExportPlatformPC {
String _get_exe_arch(const String &p_path) const;
public:
- virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
- virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) override;
+ virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+ virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) override;
virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
virtual void get_export_options(List<ExportOption> *r_options) const override;
@@ -95,7 +95,7 @@ public:
virtual int get_options_count() const override;
virtual String get_option_label(int p_index) const override;
virtual String get_option_tooltip(int p_index) const override;
- virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override;
+ virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override;
virtual void cleanup() override;
EditorExportPlatformWindows();
diff --git a/platform/windows/godot_res_wrap.rc b/platform/windows/godot_res_wrap.rc
index 27ad26cbc5..61e6100497 100644
--- a/platform/windows/godot_res_wrap.rc
+++ b/platform/windows/godot_res_wrap.rc
@@ -1,6 +1,11 @@
#include "core/version.h"
+#ifndef RT_MANIFEST
+#define RT_MANIFEST 24
+#endif
+
GODOT_ICON ICON platform/windows/godot_console.ico
+1 RT_MANIFEST "godot.manifest"
1 VERSIONINFO
FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index 40b265785f..47836788e1 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -90,6 +90,23 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
#define GetProcAddress (void *)GetProcAddress
#endif
+static String fix_path(const String &p_path) {
+ String path = p_path;
+ if (p_path.is_relative_path()) {
+ Char16String current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());
+ path = String::utf16((const char16_t *)current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace("\\", "/").path_join(path);
+ }
+ path = path.simplify_path();
+ path = path.replace("/", "\\");
+ if (!path.is_network_share_path() && !path.begins_with(R"(\\?\)")) {
+ path = R"(\\?\)" + path;
+ }
+ return path;
+}
+
static String format_error_message(DWORD id) {
LPWSTR messageBuffer = nullptr;
size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
@@ -166,15 +183,9 @@ void OS_Windows::initialize_debugging() {
static void _error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, bool p_editor_notify, ErrorHandlerType p_type) {
String err_str;
if (p_errorexp && p_errorexp[0]) {
- err_str = String::utf8(p_errorexp);
+ err_str = String::utf8(p_errorexp) + "\n";
} else {
- err_str = String::utf8(p_file) + ":" + itos(p_line) + " - " + String::utf8(p_error);
- }
-
- if (p_editor_notify) {
- err_str += " (User)\n";
- } else {
- err_str += "\n";
+ err_str = String::utf8(p_file) + ":" + itos(p_line) + " - " + String::utf8(p_error) + "\n";
}
OutputDebugStringW((LPCWSTR)err_str.utf16().ptr());
@@ -306,7 +317,7 @@ Error OS_Windows::get_entropy(uint8_t *r_buffer, int p_bytes) {
}
#ifdef DEBUG_ENABLED
-void debug_dynamic_library_check_dependencies(const String &p_root_path, const String &p_path, HashSet<String> &r_checked, HashSet<String> &r_missing) {
+void debug_dynamic_library_check_dependencies(const String &p_path, HashSet<String> &r_checked, HashSet<String> &r_missing) {
if (r_checked.has(p_path)) {
return;
}
@@ -348,15 +359,15 @@ void debug_dynamic_library_check_dependencies(const String &p_root_path, const S
const IMAGE_IMPORT_DESCRIPTOR *import_desc = (const IMAGE_IMPORT_DESCRIPTOR *)ImageDirectoryEntryToData((HMODULE)loaded_image.MappedAddress, false, IMAGE_DIRECTORY_ENTRY_IMPORT, &size);
if (import_desc) {
for (; import_desc->Name && import_desc->FirstThunk; import_desc++) {
- char16_t full_name_wc[MAX_PATH];
+ char16_t full_name_wc[32767];
const char *name_cs = (const char *)ImageRvaToVa(loaded_image.FileHeader, loaded_image.MappedAddress, import_desc->Name, nullptr);
String name = String(name_cs);
if (name.begins_with("api-ms-win-")) {
r_checked.insert(name);
- } else if (SearchPathW(nullptr, (LPCWSTR)name.utf16().get_data(), nullptr, MAX_PATH, (LPWSTR)full_name_wc, nullptr)) {
- debug_dynamic_library_check_dependencies(p_root_path, String::utf16(full_name_wc), r_checked, r_missing);
- } else if (SearchPathW((LPCWSTR)(p_path.get_base_dir().utf16().get_data()), (LPCWSTR)name.utf16().get_data(), nullptr, MAX_PATH, (LPWSTR)full_name_wc, nullptr)) {
- debug_dynamic_library_check_dependencies(p_root_path, String::utf16(full_name_wc), r_checked, r_missing);
+ } else if (SearchPathW(nullptr, (LPCWSTR)name.utf16().get_data(), nullptr, 32767, (LPWSTR)full_name_wc, nullptr)) {
+ debug_dynamic_library_check_dependencies(String::utf16(full_name_wc), r_checked, r_missing);
+ } else if (SearchPathW((LPCWSTR)(p_path.get_base_dir().utf16().get_data()), (LPCWSTR)name.utf16().get_data(), nullptr, 32767, (LPWSTR)full_name_wc, nullptr)) {
+ debug_dynamic_library_check_dependencies(String::utf16(full_name_wc), r_checked, r_missing);
} else {
r_missing.insert(name);
}
@@ -373,7 +384,7 @@ void debug_dynamic_library_check_dependencies(const String &p_root_path, const S
#endif
Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
- String path = p_path.replace("/", "\\");
+ String path = p_path;
if (!FileAccess::exists(path)) {
//this code exists so gdextension can load .dll files from within the executable path
@@ -419,11 +430,13 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha
bool has_dll_directory_api = ((add_dll_directory != nullptr) && (remove_dll_directory != nullptr));
DLL_DIRECTORY_COOKIE cookie = nullptr;
+ String dll_path = fix_path(load_path);
+ String dll_dir = fix_path(ProjectSettings::get_singleton()->globalize_path(load_path.get_base_dir()));
if (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) {
- cookie = add_dll_directory((LPCWSTR)(load_path.get_base_dir().utf16().get_data()));
+ cookie = add_dll_directory((LPCWSTR)(dll_dir.utf16().get_data()));
}
- p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(load_path.utf16().get_data()), nullptr, (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0);
+ p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(dll_path.utf16().get_data()), nullptr, (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0);
if (!p_library_handle) {
if (p_data != nullptr && p_data->generate_temp_files) {
DirAccess::remove_absolute(load_path);
@@ -434,7 +447,7 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha
HashSet<String> checked_libs;
HashSet<String> missing_libs;
- debug_dynamic_library_check_dependencies(load_path, load_path, checked_libs, missing_libs);
+ debug_dynamic_library_check_dependencies(dll_path, checked_libs, missing_libs);
if (!missing_libs.is_empty()) {
String missing;
for (const String &E : missing_libs) {
@@ -622,6 +635,72 @@ Vector<String> OS_Windows::get_video_adapter_driver_info() const {
return info;
}
+bool OS_Windows::get_user_prefers_integrated_gpu() const {
+ // On Windows 10, the preferred GPU configured in Windows Settings is
+ // stored in the registry under the key
+ // `HKEY_CURRENT_USER\SOFTWARE\Microsoft\DirectX\UserGpuPreferences`
+ // with the name being the app ID or EXE path. The value is in the form of
+ // `GpuPreference=1;`, with the value being 1 for integrated GPU and 2
+ // for discrete GPU. On Windows 11, there may be more flags, separated
+ // by semicolons.
+
+ // If this is a packaged app, use the "application user model ID".
+ // Otherwise, use the EXE path.
+ WCHAR value_name[32768];
+ bool is_packaged = false;
+ {
+ HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
+ if (kernel32) {
+ using GetCurrentApplicationUserModelIdPtr = LONG(WINAPI *)(UINT32 * length, PWSTR id);
+ GetCurrentApplicationUserModelIdPtr GetCurrentApplicationUserModelId = (GetCurrentApplicationUserModelIdPtr)GetProcAddress(kernel32, "GetCurrentApplicationUserModelId");
+
+ if (GetCurrentApplicationUserModelId) {
+ UINT32 length = sizeof(value_name) / sizeof(value_name[0]);
+ LONG result = GetCurrentApplicationUserModelId(&length, value_name);
+ if (result == ERROR_SUCCESS) {
+ is_packaged = true;
+ }
+ }
+ }
+ }
+ if (!is_packaged && GetModuleFileNameW(nullptr, value_name, sizeof(value_name) / sizeof(value_name[0])) >= sizeof(value_name) / sizeof(value_name[0])) {
+ // Paths should never be longer than 32767, but just in case.
+ return false;
+ }
+
+ LPCWSTR subkey = L"SOFTWARE\\Microsoft\\DirectX\\UserGpuPreferences";
+ HKEY hkey = nullptr;
+ LSTATUS result = RegOpenKeyExW(HKEY_CURRENT_USER, subkey, 0, KEY_READ, &hkey);
+ if (result != ERROR_SUCCESS) {
+ return false;
+ }
+
+ DWORD size = 0;
+ result = RegGetValueW(hkey, nullptr, value_name, RRF_RT_REG_SZ, nullptr, nullptr, &size);
+ if (result != ERROR_SUCCESS || size == 0) {
+ RegCloseKey(hkey);
+ return false;
+ }
+
+ Vector<WCHAR> buffer;
+ buffer.resize(size / sizeof(WCHAR));
+ result = RegGetValueW(hkey, nullptr, value_name, RRF_RT_REG_SZ, nullptr, (LPBYTE)buffer.ptrw(), &size);
+ if (result != ERROR_SUCCESS) {
+ RegCloseKey(hkey);
+ return false;
+ }
+
+ RegCloseKey(hkey);
+ const String flags = String::utf16((const char16_t *)buffer.ptr(), size / sizeof(WCHAR));
+
+ for (const String &flag : flags.split(";", false)) {
+ if (flag == "GpuPreference=1") {
+ return true;
+ }
+ }
+ return false;
+}
+
OS::DateTime OS_Windows::get_datetime(bool p_utc) const {
SYSTEMTIME systemtime;
if (p_utc) {
@@ -822,7 +901,7 @@ Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String
Dictionary ret;
- String path = p_path.replace("/", "\\");
+ String path = p_path.is_absolute_path() ? fix_path(p_path) : p_path;
String command = _quote_command_line_argument(path);
for (const String &E : p_arguments) {
command += " " + _quote_command_line_argument(E);
@@ -871,7 +950,19 @@ Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String
DWORD creation_flags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW;
- if (!CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, true, creation_flags, nullptr, nullptr, si_w, &pi.pi)) {
+ Char16String current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());
+ if (current_dir_name.size() >= MAX_PATH) {
+ Char16String current_short_dir_name;
+ str_len = GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), nullptr, 0);
+ current_short_dir_name.resize(str_len);
+ GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), (LPWSTR)current_short_dir_name.ptrw(), current_short_dir_name.size());
+ current_dir_name = current_short_dir_name;
+ }
+
+ if (!CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, true, creation_flags, nullptr, (LPWSTR)current_dir_name.ptr(), si_w, &pi.pi)) {
CLEAN_PIPES
ERR_FAIL_V_MSG(ret, "Could not create child process: " + command);
}
@@ -901,7 +992,7 @@ Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String
}
Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) {
- String path = p_path.replace("/", "\\");
+ String path = p_path.is_absolute_path() ? fix_path(p_path) : p_path;
String command = _quote_command_line_argument(path);
for (const String &E : p_arguments) {
command += " " + _quote_command_line_argument(E);
@@ -939,7 +1030,19 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
creation_flags |= CREATE_NO_WINDOW;
}
- int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, inherit_handles, creation_flags, nullptr, nullptr, si_w, &pi.pi);
+ Char16String current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());
+ if (current_dir_name.size() >= MAX_PATH) {
+ Char16String current_short_dir_name;
+ str_len = GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), nullptr, 0);
+ current_short_dir_name.resize(str_len);
+ GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), (LPWSTR)current_short_dir_name.ptrw(), current_short_dir_name.size());
+ current_dir_name = current_short_dir_name;
+ }
+
+ int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, inherit_handles, creation_flags, nullptr, (LPWSTR)current_dir_name.ptr(), si_w, &pi.pi);
if (!ret && r_pipe) {
CloseHandle(pipe[0]); // Cleanup pipe handles.
CloseHandle(pipe[1]);
@@ -1003,7 +1106,7 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
}
Error OS_Windows::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) {
- String path = p_path.replace("/", "\\");
+ String path = p_path.is_absolute_path() ? fix_path(p_path) : p_path;
String command = _quote_command_line_argument(path);
for (const String &E : p_arguments) {
command += " " + _quote_command_line_argument(E);
@@ -1022,7 +1125,19 @@ Error OS_Windows::create_process(const String &p_path, const List<String> &p_arg
creation_flags |= CREATE_NO_WINDOW;
}
- int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, creation_flags, nullptr, nullptr, si_w, &pi.pi);
+ Char16String current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());
+ if (current_dir_name.size() >= MAX_PATH) {
+ Char16String current_short_dir_name;
+ str_len = GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), nullptr, 0);
+ current_short_dir_name.resize(str_len);
+ GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), (LPWSTR)current_short_dir_name.ptrw(), current_short_dir_name.size());
+ current_dir_name = current_short_dir_name;
+ }
+
+ int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, creation_flags, nullptr, (LPWSTR)current_dir_name.ptr(), si_w, &pi.pi);
ERR_FAIL_COND_V_MSG(ret == 0, ERR_CANT_FORK, "Could not create child process: " + command);
ProcessID pid = pi.pi.dwProcessId;
@@ -1390,8 +1505,8 @@ Vector<String> OS_Windows::get_system_font_path_for_text(const String &p_font_na
continue;
}
- WCHAR file_path[MAX_PATH];
- hr = loader->GetFilePathFromKey(reference_key, reference_key_size, &file_path[0], MAX_PATH);
+ WCHAR file_path[32767];
+ hr = loader->GetFilePathFromKey(reference_key, reference_key_size, &file_path[0], 32767);
if (FAILED(hr)) {
continue;
}
@@ -1469,8 +1584,8 @@ String OS_Windows::get_system_font_path(const String &p_font_name, int p_weight,
continue;
}
- WCHAR file_path[MAX_PATH];
- hr = loader->GetFilePathFromKey(reference_key, reference_key_size, &file_path[0], MAX_PATH);
+ WCHAR file_path[32767];
+ hr = loader->GetFilePathFromKey(reference_key, reference_key_size, &file_path[0], 32767);
if (FAILED(hr)) {
continue;
}
@@ -1576,7 +1691,7 @@ Error OS_Windows::shell_show_in_file_manager(String p_path, bool p_open_folder)
if (!p_path.is_quoted()) {
p_path = p_path.quote();
}
- p_path = p_path.replace("/", "\\");
+ p_path = fix_path(p_path);
INT_PTR ret = OK;
if (open_folder) {
@@ -1901,6 +2016,19 @@ String OS_Windows::get_system_ca_certificates() {
OS_Windows::OS_Windows(HINSTANCE _hInstance) {
hInstance = _hInstance;
+ // Reset CWD to ensure long path is used.
+ Char16String current_dir_name;
+ size_t str_len = GetCurrentDirectoryW(0, nullptr);
+ current_dir_name.resize(str_len + 1);
+ GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());
+
+ Char16String new_current_dir_name;
+ str_len = GetLongPathNameW((LPCWSTR)current_dir_name.get_data(), nullptr, 0);
+ new_current_dir_name.resize(str_len + 1);
+ GetLongPathNameW((LPCWSTR)current_dir_name.get_data(), (LPWSTR)new_current_dir_name.ptrw(), new_current_dir_name.size());
+
+ SetCurrentDirectoryW((LPCWSTR)new_current_dir_name.get_data());
+
#ifndef WINDOWS_SUBSYSTEM_CONSOLE
RedirectIOToConsole();
#endif
diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h
index b6a21ed42d..9c7b98d7fd 100644
--- a/platform/windows/os_windows.h
+++ b/platform/windows/os_windows.h
@@ -172,6 +172,7 @@ public:
virtual String get_version() const override;
virtual Vector<String> get_video_adapter_driver_info() const override;
+ virtual bool get_user_prefers_integrated_gpu() const override;
virtual void initialize_joypads() override {}
diff --git a/platform/windows/windows_utils.cpp b/platform/windows/windows_utils.cpp
index 9e0b9eed8a..30743c6900 100644
--- a/platform/windows/windows_utils.cpp
+++ b/platform/windows/windows_utils.cpp
@@ -155,7 +155,11 @@ Error WindowsUtils::copy_and_rename_pdb(const String &p_dll_path) {
} else if (!FileAccess::exists(copy_pdb_path)) {
copy_pdb_path = dll_base_dir.path_join(copy_pdb_path.get_file());
}
- ERR_FAIL_COND_V_MSG(!FileAccess::exists(copy_pdb_path), FAILED, vformat("File '%s' does not exist.", copy_pdb_path));
+ if (!FileAccess::exists(copy_pdb_path)) {
+ // The PDB file may be distributed separately on purpose, so we don't consider this an error.
+ WARN_VERBOSE(vformat("PDB file '%s' for library '%s' was not found, skipping copy/rename.", copy_pdb_path, p_dll_path));
+ return ERR_SKIP;
+ }
String new_pdb_base_name = p_dll_path.get_file().get_basename() + "_";
diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp
index 89a0479de3..7c60e47e64 100644
--- a/scene/2d/audio_stream_player_2d.cpp
+++ b/scene/2d/audio_stream_player_2d.cpp
@@ -276,10 +276,6 @@ void AudioStreamPlayer2D::_set_playing(bool p_enable) {
internal->set_playing(p_enable);
}
-bool AudioStreamPlayer2D::_is_active() const {
- return internal->is_active();
-}
-
void AudioStreamPlayer2D::_validate_property(PropertyInfo &p_property) const {
internal->validate_property(p_property);
}
@@ -385,8 +381,7 @@ void AudioStreamPlayer2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_autoplay", "enable"), &AudioStreamPlayer2D::set_autoplay);
ClassDB::bind_method(D_METHOD("is_autoplay_enabled"), &AudioStreamPlayer2D::is_autoplay_enabled);
- ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer2D::_set_playing);
- ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer2D::_is_active);
+ ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer2D::_set_playing);
ClassDB::bind_method(D_METHOD("set_max_distance", "pixels"), &AudioStreamPlayer2D::set_max_distance);
ClassDB::bind_method(D_METHOD("get_max_distance"), &AudioStreamPlayer2D::get_max_distance);
@@ -415,7 +410,7 @@ void AudioStreamPlayer2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,suffix:dB"), "set_volume_db", "get_volume_db");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "1,4096,1,or_greater,exp,suffix:px"), "set_max_distance", "get_max_distance");
diff --git a/scene/2d/parallax_2d.cpp b/scene/2d/parallax_2d.cpp
index 9dd9d4a376..fdb2d2cdd0 100644
--- a/scene/2d/parallax_2d.cpp
+++ b/scene/2d/parallax_2d.cpp
@@ -47,9 +47,18 @@ void Parallax2D::_notification(int p_what) {
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
- autoscroll_offset += autoscroll * get_process_delta_time();
- autoscroll_offset = autoscroll_offset.posmodv(repeat_size);
+ Point2 offset = scroll_offset;
+ offset += autoscroll * get_process_delta_time();
+ if (repeat_size.x) {
+ offset.x = Math::fposmod(offset.x, repeat_size.x);
+ }
+
+ if (repeat_size.y) {
+ offset.y = Math::fposmod(offset.y, repeat_size.y);
+ }
+
+ scroll_offset = offset;
_update_scroll();
} break;
@@ -106,14 +115,14 @@ void Parallax2D::_update_scroll() {
scroll_ofs *= scroll_scale;
if (repeat_size.x) {
- real_t mod = Math::fposmod(scroll_ofs.x - scroll_offset.x - autoscroll_offset.x, repeat_size.x * get_scale().x);
+ real_t mod = Math::fposmod(scroll_ofs.x - scroll_offset.x, repeat_size.x * get_scale().x);
scroll_ofs.x = screen_offset.x - mod;
} else {
scroll_ofs.x = screen_offset.x + scroll_offset.x - scroll_ofs.x;
}
if (repeat_size.y) {
- real_t mod = Math::fposmod(scroll_ofs.y - scroll_offset.y - autoscroll_offset.y, repeat_size.y * get_scale().y);
+ real_t mod = Math::fposmod(scroll_ofs.y - scroll_offset.y, repeat_size.y * get_scale().y);
scroll_ofs.y = screen_offset.y - mod;
} else {
scroll_ofs.y = screen_offset.y + scroll_offset.y - scroll_ofs.y;
@@ -193,7 +202,6 @@ void Parallax2D::set_autoscroll(const Point2 &p_autoscroll) {
}
autoscroll = p_autoscroll;
- autoscroll_offset = Point2();
_update_process();
_update_scroll();
diff --git a/scene/2d/parallax_2d.h b/scene/2d/parallax_2d.h
index 5fbc3a20c8..f15e3fa9ff 100644
--- a/scene/2d/parallax_2d.h
+++ b/scene/2d/parallax_2d.h
@@ -47,7 +47,6 @@ class Parallax2D : public Node2D {
Point2 limit_begin = Point2(-DEFAULT_LIMIT, -DEFAULT_LIMIT);
Point2 limit_end = Point2(DEFAULT_LIMIT, DEFAULT_LIMIT);
Point2 autoscroll;
- Point2 autoscroll_offset;
bool follow_viewport = true;
bool ignore_camera_scroll = false;
diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp
index 5ea5098475..920f5720fa 100644
--- a/scene/2d/remote_transform_2d.cpp
+++ b/scene/2d/remote_transform_2d.cpp
@@ -114,6 +114,16 @@ void RemoteTransform2D::_notification(int p_what) {
_update_cache();
} break;
+ case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+ if (cache.is_valid()) {
+ _update_remote();
+ Node2D *n = Object::cast_to<Node2D>(ObjectDB::get_instance(cache));
+ if (n) {
+ n->reset_physics_interpolation();
+ }
+ }
+ } break;
+
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED:
case NOTIFICATION_TRANSFORM_CHANGED: {
if (!is_inside_tree()) {
diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp
index 7b125a6895..ba0958e74b 100644
--- a/scene/2d/tile_map_layer.cpp
+++ b/scene/2d/tile_map_layer.cpp
@@ -411,7 +411,7 @@ void TileMapLayer::_rendering_update(bool p_force_cleanup) {
}
// ----------- Occluders processing -----------
- if (forced_cleanup) {
+ if (forced_cleanup || !occlusion_enabled) {
// Clean everything.
for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) {
_rendering_occluders_clear_cell(kv.value);
@@ -433,7 +433,7 @@ void TileMapLayer::_rendering_update(bool p_force_cleanup) {
// -----------
// Mark the rendering state as up to date.
- _rendering_was_cleaned_up = forced_cleanup;
+ _rendering_was_cleaned_up = forced_cleanup || !occlusion_enabled;
}
void TileMapLayer::_rendering_notification(int p_what) {
@@ -957,7 +957,7 @@ void TileMapLayer::_navigation_update(bool p_force_cleanup) {
// Create a dedicated map for each layer.
RID new_layer_map = ns->map_create();
// Set the default NavigationPolygon cell_size on the new map as a mismatch causes an error.
- ns->map_set_cell_size(new_layer_map, 1.0);
+ ns->map_set_cell_size(new_layer_map, NavigationDefaults2D::navmesh_cell_size);
ns->map_set_active(new_layer_map, true);
navigation_map_override = new_layer_map;
}
@@ -1828,6 +1828,9 @@ void TileMapLayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_collision_visibility_mode", "visibility_mode"), &TileMapLayer::set_collision_visibility_mode);
ClassDB::bind_method(D_METHOD("get_collision_visibility_mode"), &TileMapLayer::get_collision_visibility_mode);
+ ClassDB::bind_method(D_METHOD("set_occlusion_enabled", "enabled"), &TileMapLayer::set_occlusion_enabled);
+ ClassDB::bind_method(D_METHOD("is_occlusion_enabled"), &TileMapLayer::is_occlusion_enabled);
+
ClassDB::bind_method(D_METHOD("set_navigation_enabled", "enabled"), &TileMapLayer::set_navigation_enabled);
ClassDB::bind_method(D_METHOD("is_navigation_enabled"), &TileMapLayer::is_navigation_enabled);
ClassDB::bind_method(D_METHOD("set_navigation_map", "map"), &TileMapLayer::set_navigation_map);
@@ -1843,6 +1846,7 @@ void TileMapLayer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tile_set", PROPERTY_HINT_RESOURCE_TYPE, "TileSet"), "set_tile_set", "get_tile_set");
ADD_GROUP("Rendering", "");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "occlusion_enabled"), "set_occlusion_enabled", "is_occlusion_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "y_sort_origin"), "set_y_sort_origin", "get_y_sort_origin");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "x_draw_order_reversed"), "set_x_draw_order_reversed", "is_x_draw_order_reversed");
ADD_PROPERTY(PropertyInfo(Variant::INT, "rendering_quadrant_size"), "set_rendering_quadrant_size", "get_rendering_quadrant_size");
@@ -2960,6 +2964,20 @@ TileMapLayer::DebugVisibilityMode TileMapLayer::get_collision_visibility_mode()
return collision_visibility_mode;
}
+void TileMapLayer::set_occlusion_enabled(bool p_enabled) {
+ if (occlusion_enabled == p_enabled) {
+ return;
+ }
+ occlusion_enabled = p_enabled;
+ dirty.flags[DIRTY_FLAGS_LAYER_OCCLUSION_ENABLED] = true;
+ _queue_internal_update();
+ emit_signal(CoreStringName(changed));
+}
+
+bool TileMapLayer::is_occlusion_enabled() const {
+ return occlusion_enabled;
+}
+
void TileMapLayer::set_navigation_enabled(bool p_enabled) {
if (navigation_enabled == p_enabled) {
return;
diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h
index 1a6d182094..2a986667bd 100644
--- a/scene/2d/tile_map_layer.h
+++ b/scene/2d/tile_map_layer.h
@@ -254,6 +254,7 @@ public:
DIRTY_FLAGS_LAYER_COLLISION_ENABLED,
DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES,
DIRTY_FLAGS_LAYER_COLLISION_VISIBILITY_MODE,
+ DIRTY_FLAGS_LAYER_OCCLUSION_ENABLED,
DIRTY_FLAGS_LAYER_NAVIGATION_ENABLED,
DIRTY_FLAGS_LAYER_NAVIGATION_MAP,
DIRTY_FLAGS_LAYER_NAVIGATION_VISIBILITY_MODE,
@@ -288,6 +289,8 @@ private:
bool use_kinematic_bodies = false;
DebugVisibilityMode collision_visibility_mode = DEBUG_VISIBILITY_MODE_DEFAULT;
+ bool occlusion_enabled = true;
+
bool navigation_enabled = true;
RID navigation_map_override;
DebugVisibilityMode navigation_visibility_mode = DEBUG_VISIBILITY_MODE_DEFAULT;
@@ -497,6 +500,9 @@ public:
void set_collision_visibility_mode(DebugVisibilityMode p_show_collision);
DebugVisibilityMode get_collision_visibility_mode() const;
+ void set_occlusion_enabled(bool p_enabled);
+ bool is_occlusion_enabled() const;
+
void set_navigation_enabled(bool p_enabled);
bool is_navigation_enabled() const;
void set_navigation_map(RID p_map);
diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp
index 4d3f494ccf..591528b915 100644
--- a/scene/3d/audio_stream_player_3d.cpp
+++ b/scene/3d/audio_stream_player_3d.cpp
@@ -596,10 +596,6 @@ void AudioStreamPlayer3D::_set_playing(bool p_enable) {
internal->set_playing(p_enable);
}
-bool AudioStreamPlayer3D::_is_active() const {
- return internal->is_active();
-}
-
void AudioStreamPlayer3D::_validate_property(PropertyInfo &p_property) const {
internal->validate_property(p_property);
}
@@ -779,8 +775,7 @@ void AudioStreamPlayer3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_autoplay", "enable"), &AudioStreamPlayer3D::set_autoplay);
ClassDB::bind_method(D_METHOD("is_autoplay_enabled"), &AudioStreamPlayer3D::is_autoplay_enabled);
- ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer3D::_set_playing);
- ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer3D::_is_active);
+ ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer3D::_set_playing);
ClassDB::bind_method(D_METHOD("set_max_distance", "meters"), &AudioStreamPlayer3D::set_max_distance);
ClassDB::bind_method(D_METHOD("get_max_distance"), &AudioStreamPlayer3D::get_max_distance);
@@ -830,7 +825,7 @@ void AudioStreamPlayer3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_size", PROPERTY_HINT_RANGE, "0.1,100,0.01,or_greater"), "set_unit_size", "get_unit_size");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_db", PROPERTY_HINT_RANGE, "-24,6,suffix:dB"), "set_max_db", "get_max_db");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,4096,0.01,or_greater,suffix:m"), "set_max_distance", "get_max_distance");
diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp
index 03fe5e1fad..acbc443a93 100644
--- a/scene/3d/cpu_particles_3d.cpp
+++ b/scene/3d/cpu_particles_3d.cpp
@@ -448,6 +448,10 @@ void CPUParticles3D::set_emission_ring_inner_radius(real_t p_radius) {
emission_ring_inner_radius = p_radius;
}
+void CPUParticles3D::set_emission_ring_cone_angle(real_t p_angle) {
+ emission_ring_cone_angle = p_angle;
+}
+
void CPUParticles3D::set_scale_curve_x(Ref<Curve> p_scale_curve) {
scale_curve_x = p_scale_curve;
}
@@ -501,6 +505,10 @@ real_t CPUParticles3D::get_emission_ring_inner_radius() const {
return emission_ring_inner_radius;
}
+real_t CPUParticles3D::get_emission_ring_cone_angle() const {
+ return emission_ring_cone_angle;
+}
+
CPUParticles3D::EmissionShape CPUParticles3D::get_emission_shape() const {
return emission_shape;
}
@@ -878,8 +886,14 @@ void CPUParticles3D::_particles_process(double p_delta) {
}
} break;
case EMISSION_SHAPE_RING: {
+ real_t radius_clamped = MAX(0.001, emission_ring_radius);
+ real_t top_radius = MAX(radius_clamped - Math::tan(Math::deg_to_rad(90.0 - emission_ring_cone_angle)) * emission_ring_height, 0.0);
+ real_t y_pos = Math::randf();
+ real_t skew = MAX(MIN(radius_clamped, top_radius) / MAX(radius_clamped, top_radius), 0.5);
+ y_pos = radius_clamped < top_radius ? Math::pow(y_pos, skew) : 1.0 - Math::pow(y_pos, skew);
real_t ring_random_angle = Math::randf() * Math_TAU;
- real_t ring_random_radius = Math::sqrt(Math::randf() * (emission_ring_radius * emission_ring_radius - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);
+ real_t ring_random_radius = Math::sqrt(Math::randf() * (radius_clamped * radius_clamped - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);
+ ring_random_radius = Math::lerp(ring_random_radius, ring_random_radius * (top_radius / radius_clamped), y_pos);
Vector3 axis = emission_ring_axis == Vector3(0.0, 0.0, 0.0) ? Vector3(0.0, 0.0, 1.0) : emission_ring_axis.normalized();
Vector3 ortho_axis;
if (axis.abs() == Vector3(1.0, 0.0, 0.0)) {
@@ -890,7 +904,7 @@ void CPUParticles3D::_particles_process(double p_delta) {
ortho_axis = ortho_axis.normalized();
ortho_axis.rotate(axis, ring_random_angle);
ortho_axis = ortho_axis.normalized();
- p.transform.origin = ortho_axis * ring_random_radius + (Math::randf() * emission_ring_height - emission_ring_height / 2.0) * axis;
+ p.transform.origin = ortho_axis * ring_random_radius + (y_pos * emission_ring_height - emission_ring_height / 2.0) * axis;
} break;
case EMISSION_SHAPE_MAX: { // Max value for validity check.
break;
@@ -1550,6 +1564,9 @@ void CPUParticles3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &CPUParticles3D::set_emission_ring_inner_radius);
ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &CPUParticles3D::get_emission_ring_inner_radius);
+ ClassDB::bind_method(D_METHOD("set_emission_ring_cone_angle", "cone_angle"), &CPUParticles3D::set_emission_ring_cone_angle);
+ ClassDB::bind_method(D_METHOD("get_emission_ring_cone_angle"), &CPUParticles3D::get_emission_ring_cone_angle);
+
ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles3D::get_gravity);
ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles3D::set_gravity);
@@ -1577,9 +1594,10 @@ void CPUParticles3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "emission_normals"), "set_emission_normals", "get_emission_normals");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "emission_colors"), "set_emission_colors", "get_emission_colors");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_ring_axis"), "set_emission_ring_axis", "get_emission_ring_axis");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_height", "get_emission_ring_height");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_radius", "get_emission_ring_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_cone_angle", PROPERTY_HINT_RANGE, "0,90,0.01,degrees"), "set_emission_ring_cone_angle", "get_emission_ring_cone_angle");
ADD_GROUP("Particle Flags", "particle_flag_");
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_align_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_rotate_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ROTATE_Y);
@@ -1716,6 +1734,7 @@ CPUParticles3D::CPUParticles3D() {
set_emission_ring_height(1);
set_emission_ring_radius(1);
set_emission_ring_inner_radius(0);
+ set_emission_ring_cone_angle(90);
set_gravity(Vector3(0, -9.8, 0));
diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h
index 82ea4bbef3..978bb64e71 100644
--- a/scene/3d/cpu_particles_3d.h
+++ b/scene/3d/cpu_particles_3d.h
@@ -178,6 +178,7 @@ private:
real_t emission_ring_height = 0.0;
real_t emission_ring_radius = 0.0;
real_t emission_ring_inner_radius = 0.0;
+ real_t emission_ring_cone_angle = 0.0;
Ref<Curve> scale_curve_x;
Ref<Curve> scale_curve_y;
@@ -282,6 +283,7 @@ public:
void set_emission_ring_height(real_t p_height);
void set_emission_ring_radius(real_t p_radius);
void set_emission_ring_inner_radius(real_t p_radius);
+ void set_emission_ring_cone_angle(real_t p_angle);
void set_scale_curve_x(Ref<Curve> p_scale_curve);
void set_scale_curve_y(Ref<Curve> p_scale_curve);
void set_scale_curve_z(Ref<Curve> p_scale_curve);
@@ -297,6 +299,7 @@ public:
real_t get_emission_ring_height() const;
real_t get_emission_ring_radius() const;
real_t get_emission_ring_inner_radius() const;
+ real_t get_emission_ring_cone_angle() const;
Ref<Curve> get_scale_curve_x() const;
Ref<Curve> get_scale_curve_y() const;
Ref<Curve> get_scale_curve_z() const;
diff --git a/scene/3d/physics/vehicle_body_3d.cpp b/scene/3d/physics/vehicle_body_3d.cpp
index c23032d3b9..b4c321cf5f 100644
--- a/scene/3d/physics/vehicle_body_3d.cpp
+++ b/scene/3d/physics/vehicle_body_3d.cpp
@@ -219,6 +219,14 @@ bool VehicleWheel3D::is_in_contact() const {
return m_raycastInfo.m_isInContact;
}
+Vector3 VehicleWheel3D::get_contact_point() const {
+ return m_raycastInfo.m_contactPointWS;
+}
+
+Vector3 VehicleWheel3D::get_contact_normal() const {
+ return m_raycastInfo.m_contactNormalWS;
+}
+
Node3D *VehicleWheel3D::get_contact_body() const {
return m_raycastInfo.m_groundObject;
}
@@ -256,6 +264,8 @@ void VehicleWheel3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_in_contact"), &VehicleWheel3D::is_in_contact);
ClassDB::bind_method(D_METHOD("get_contact_body"), &VehicleWheel3D::get_contact_body);
+ ClassDB::bind_method(D_METHOD("get_contact_point"), &VehicleWheel3D::get_contact_point);
+ ClassDB::bind_method(D_METHOD("get_contact_normal"), &VehicleWheel3D::get_contact_normal);
ClassDB::bind_method(D_METHOD("set_roll_influence", "roll_influence"), &VehicleWheel3D::set_roll_influence);
ClassDB::bind_method(D_METHOD("get_roll_influence"), &VehicleWheel3D::get_roll_influence);
diff --git a/scene/3d/physics/vehicle_body_3d.h b/scene/3d/physics/vehicle_body_3d.h
index def9984440..24f120ed26 100644
--- a/scene/3d/physics/vehicle_body_3d.h
+++ b/scene/3d/physics/vehicle_body_3d.h
@@ -130,6 +130,10 @@ public:
bool is_in_contact() const;
+ Vector3 get_contact_point() const;
+
+ Vector3 get_contact_normal() const;
+
Node3D *get_contact_body() const;
void set_roll_influence(real_t p_value);
diff --git a/scene/3d/remote_transform_3d.cpp b/scene/3d/remote_transform_3d.cpp
index 8d6e717132..e580882c46 100644
--- a/scene/3d/remote_transform_3d.cpp
+++ b/scene/3d/remote_transform_3d.cpp
@@ -113,6 +113,16 @@ void RemoteTransform3D::_notification(int p_what) {
_update_cache();
} break;
+ case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+ if (cache.is_valid()) {
+ _update_remote();
+ Node3D *n = Object::cast_to<Node3D>(ObjectDB::get_instance(cache));
+ if (n) {
+ n->reset_physics_interpolation();
+ }
+ }
+ } break;
+
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED:
case NOTIFICATION_TRANSFORM_CHANGED: {
if (!is_inside_tree()) {
diff --git a/scene/3d/voxelizer.h b/scene/3d/voxelizer.h
index 6ea1cfdbb0..08d018eee9 100644
--- a/scene/3d/voxelizer.h
+++ b/scene/3d/voxelizer.h
@@ -35,9 +35,8 @@
class Voxelizer {
private:
- enum {
+ enum : uint32_t {
CHILD_EMPTY = 0xFFFFFFFF
-
};
struct Cell {
diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp
index 20dd12f8c3..0ab4dbe470 100644
--- a/scene/animation/animation_mixer.cpp
+++ b/scene/animation/animation_mixer.cpp
@@ -947,14 +947,6 @@ void AnimationMixer::_process_animation(double p_delta, bool p_update_only) {
clear_animation_instances();
}
-Variant AnimationMixer::post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) {
- Variant res;
- if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, p_object_id, p_object_sub_idx, res)) {
- return res;
- }
- return _post_process_key_value(p_anim, p_track, p_value, p_object_id, p_object_sub_idx);
-}
-
Variant AnimationMixer::_post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) {
#ifndef _3D_DISABLED
switch (p_anim->track_get_type(p_track)) {
@@ -974,6 +966,17 @@ Variant AnimationMixer::_post_process_key_value(const Ref<Animation> &p_anim, in
return p_value;
}
+Variant AnimationMixer::post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) {
+ if (is_GDVIRTUAL_CALL_post_process_key_value) {
+ Variant res;
+ if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, p_object_id, p_object_sub_idx, res)) {
+ return res;
+ }
+ is_GDVIRTUAL_CALL_post_process_key_value = false;
+ }
+ return _post_process_key_value(p_anim, p_track, p_value, p_object_id, p_object_sub_idx);
+}
+
void AnimationMixer::_blend_init() {
// Check all tracks, see if they need modification.
root_motion_position = Vector3(0, 0, 0);
@@ -1080,22 +1083,26 @@ void AnimationMixer::_blend_calc_total_weight() {
for (const AnimationInstance &ai : animation_instances) {
Ref<Animation> a = ai.animation_data.animation;
real_t weight = ai.playback_info.weight;
- Vector<real_t> track_weights = ai.playback_info.track_weights;
- Vector<Animation::TypeHash> processed_hashes;
- for (int i = 0; i < a->get_track_count(); i++) {
- if (!a->track_is_enabled(i)) {
+ const real_t *track_weights_ptr = ai.playback_info.track_weights.ptr();
+ int track_weights_count = ai.playback_info.track_weights.size();
+ static LocalVector<Animation::TypeHash> processed_hashes;
+ processed_hashes.clear();
+ const Vector<Animation::Track *> tracks = a->get_tracks();
+ for (const Animation::Track *animation_track : tracks) {
+ if (!animation_track->enabled) {
continue;
}
- Animation::TypeHash thash = a->track_get_type_hash(i);
- if (!track_cache.has(thash) || processed_hashes.has(thash)) {
+ Animation::TypeHash thash = animation_track->thash;
+ TrackCache **track_ptr = track_cache.getptr(thash);
+ if (track_ptr == nullptr || processed_hashes.has(thash)) {
// No path, but avoid error spamming.
// Or, there is the case different track type with same path; These can be distinguished by hash. So don't add the weight doubly.
continue;
}
- TrackCache *track = track_cache[thash];
+ TrackCache *track = *track_ptr;
int blend_idx = track_map[track->path];
ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count);
- real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight;
+ real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight;
track->total_weight += blend;
processed_hashes.push_back(thash);
}
@@ -1115,26 +1122,33 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
Animation::LoopedFlag looped_flag = ai.playback_info.looped_flag;
bool is_external_seeking = ai.playback_info.is_external_seeking;
real_t weight = ai.playback_info.weight;
- Vector<real_t> track_weights = ai.playback_info.track_weights;
+ const real_t *track_weights_ptr = ai.playback_info.track_weights.ptr();
+ int track_weights_count = ai.playback_info.track_weights.size();
bool backward = signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream.
bool seeked_backward = signbit(p_delta);
#ifndef _3D_DISABLED
bool calc_root = !seeked || is_external_seeking;
#endif // _3D_DISABLED
-
- for (int i = 0; i < a->get_track_count(); i++) {
- if (!a->track_is_enabled(i)) {
+ const Vector<Animation::Track *> tracks = a->get_tracks();
+ Animation::Track *const *tracks_ptr = tracks.ptr();
+ real_t a_length = a->get_length();
+ int count = tracks.size();
+ for (int i = 0; i < count; i++) {
+ const Animation::Track *animation_track = tracks_ptr[i];
+ if (!animation_track->enabled) {
continue;
}
- Animation::TypeHash thash = a->track_get_type_hash(i);
- if (!track_cache.has(thash)) {
+ Animation::TypeHash thash = animation_track->thash;
+ TrackCache **track_ptr = track_cache.getptr(thash);
+ if (track_ptr == nullptr) {
continue; // No path, but avoid error spamming.
}
- TrackCache *track = track_cache[thash];
- ERR_CONTINUE(!track_map.has(track->path));
- int blend_idx = track_map[track->path];
+ TrackCache *track = *track_ptr;
+ int *blend_idx_ptr = track_map.getptr(track->path);
+ ERR_CONTINUE(blend_idx_ptr == nullptr);
+ int blend_idx = *blend_idx_ptr;
ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count);
- real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight;
+ real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight;
if (!deterministic) {
// If non-deterministic, do normalization.
// It would be better to make this if statement outside the for loop, but come here since too much code...
@@ -1143,8 +1157,8 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
blend = blend / track->total_weight;
}
- Animation::TrackType ttype = a->track_get_type(i);
- track->root_motion = root_motion_track == a->track_get_path(i);
+ Animation::TrackType ttype = animation_track->type;
+ track->root_motion = root_motion_track == animation_track->path;
switch (ttype) {
case Animation::TYPE_POSITION_3D: {
#ifndef _3D_DISABLED
@@ -1161,26 +1175,26 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
prev_time = 0;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
}
}
} else {
- if (Animation::is_greater_approx(prev_time, (double)a->get_length())) {
+ if (Animation::is_greater_approx(prev_time, (double)a_length)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
@@ -1195,7 +1209,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
- a->try_position_track_interpolate(i, (double)a->get_length(), &loc[1]);
+ a->try_position_track_interpolate(i, (double)a_length, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
prev_time = 0;
@@ -1210,7 +1224,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_position_track_interpolate(i, 0, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
}
}
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
@@ -1221,7 +1235,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_position_track_interpolate(i, time, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
- prev_time = !backward ? 0 : (double)a->get_length();
+ prev_time = !backward ? 0 : (double)a_length;
}
{
Vector3 loc;
@@ -1249,26 +1263,26 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
prev_time = 0;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
}
}
} else {
- if (Animation::is_greater_approx(prev_time, (double)a->get_length())) {
+ if (Animation::is_greater_approx(prev_time, (double)a_length)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
@@ -1283,7 +1297,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx);
- a->try_rotation_track_interpolate(i, (double)a->get_length(), &rot[1]);
+ a->try_rotation_track_interpolate(i, (double)a_length, &rot[1]);
rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx);
root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
prev_time = 0;
@@ -1297,7 +1311,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx);
a->try_rotation_track_interpolate(i, 0, &rot[1]);
root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
}
}
Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]);
@@ -1308,7 +1322,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_rotation_track_interpolate(i, time, &rot[1]);
rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx);
root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
- prev_time = !backward ? 0 : (double)a->get_length();
+ prev_time = !backward ? 0 : (double)a_length;
}
{
Quaternion rot;
@@ -1336,26 +1350,26 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
prev_time = 0;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
}
}
} else {
- if (Animation::is_greater_approx(prev_time, (double)a->get_length())) {
+ if (Animation::is_greater_approx(prev_time, (double)a_length)) {
switch (a->get_loop_mode()) {
case Animation::LOOP_NONE: {
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
} break;
case Animation::LOOP_LINEAR: {
- prev_time = Math::fposmod(prev_time, (double)a->get_length());
+ prev_time = Math::fposmod(prev_time, (double)a_length);
} break;
case Animation::LOOP_PINGPONG: {
- prev_time = Math::pingpong(prev_time, (double)a->get_length());
+ prev_time = Math::pingpong(prev_time, (double)a_length);
} break;
default:
break;
@@ -1370,7 +1384,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
continue;
}
scale[0] = post_process_key_value(a, i, scale[0], t->object_id, t->bone_idx);
- a->try_scale_track_interpolate(i, (double)a->get_length(), &scale[1]);
+ a->try_scale_track_interpolate(i, (double)a_length, &scale[1]);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx);
prev_time = 0;
@@ -1385,7 +1399,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_scale_track_interpolate(i, 0, &scale[1]);
scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
- prev_time = (double)a->get_length();
+ prev_time = (double)a_length;
}
}
Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]);
@@ -1396,7 +1410,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
a->try_scale_track_interpolate(i, time, &scale[1]);
scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
- prev_time = !backward ? 0 : (double)a->get_length();
+ prev_time = !backward ? 0 : (double)a_length;
}
{
Vector3 scale;
@@ -1560,7 +1574,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
PlayingAudioTrackInfo &track_info = t->playing_streams[oid];
- track_info.length = a->get_length();
+ track_info.length = a_length;
track_info.time = time;
track_info.volume += blend;
track_info.loop = a->get_loop_mode() != Animation::LOOP_NONE;
@@ -1679,7 +1693,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); // Seek to loop.
} break;
case Animation::LOOP_PINGPONG: {
- at_anim_pos = Math::pingpong(time - pos, (double)a->get_length());
+ at_anim_pos = Math::pingpong(time - pos, (double)a_length);
} break;
default:
break;
@@ -1717,6 +1731,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
}
}
+ is_GDVIRTUAL_CALL_post_process_key_value = true;
}
void AnimationMixer::_blend_apply() {
diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h
index c029c68ae1..5482197fbd 100644
--- a/scene/animation/animation_mixer.h
+++ b/scene/animation/animation_mixer.h
@@ -48,6 +48,7 @@ class AnimationMixer : public Node {
#endif // TOOLS_ENABLED
bool reset_on_save = true;
+ bool is_GDVIRTUAL_CALL_post_process_key_value = true;
public:
enum AnimationCallbackModeProcess {
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index c5a6a99d07..867bbda4b3 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -203,10 +203,11 @@ AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref<AnimationNode> p_node
}
for (const KeyValue<NodePath, bool> &E : filter) {
- if (!process_state->track_map.has(E.key)) {
+ const HashMap<NodePath, int> &map = *process_state->track_map;
+ if (!map.has(E.key)) {
continue;
}
- int idx = process_state->track_map[E.key];
+ int idx = map[E.key];
blendw[idx] = 1.0; // Filtered goes to one.
}
@@ -618,7 +619,7 @@ bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const
process_state.valid = true;
process_state.invalid_reasons = "";
process_state.last_pass = process_pass;
- process_state.track_map = p_track_map;
+ process_state.track_map = &p_track_map;
// Init node state for root AnimationNode.
root_animation_node->node_state.track_weights.resize(p_track_count);
diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h
index aa497ff1d6..d4b7bf31c9 100644
--- a/scene/animation/animation_tree.h
+++ b/scene/animation/animation_tree.h
@@ -106,7 +106,7 @@ public:
// Temporary state for blending process which needs to be started in the AnimationTree, pass through the AnimationNodes, and then return to the AnimationTree.
struct ProcessState {
AnimationTree *tree = nullptr;
- HashMap<NodePath, int> track_map; // TODO: Is there a better way to manage filter/tracks?
+ const HashMap<NodePath, int> *track_map; // TODO: Is there a better way to manage filter/tracks?
bool is_testing = false;
bool valid = false;
String invalid_reasons;
diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp
index f8bbd704f4..e1fd8abede 100644
--- a/scene/animation/tween.cpp
+++ b/scene/animation/tween.cpp
@@ -579,6 +579,7 @@ bool PropertyTweener::step(double &r_delta) {
Object *target_instance = ObjectDB::get_instance(target);
if (!target_instance) {
+ _finish();
return false;
}
elapsed_time += r_delta;
@@ -706,6 +707,7 @@ bool CallbackTweener::step(double &r_delta) {
}
if (!callback.is_valid()) {
+ _finish();
return false;
}
@@ -770,6 +772,7 @@ bool MethodTweener::step(double &r_delta) {
}
if (!callback.is_valid()) {
+ _finish();
return false;
}
diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp
index 183c4af950..d4b44a8b69 100644
--- a/scene/audio/audio_stream_player.cpp
+++ b/scene/audio/audio_stream_player.cpp
@@ -154,10 +154,6 @@ void AudioStreamPlayer::_set_playing(bool p_enable) {
internal->set_playing(p_enable);
}
-bool AudioStreamPlayer::_is_active() const {
- return internal->is_active();
-}
-
void AudioStreamPlayer::set_stream_paused(bool p_pause) {
internal->set_stream_paused(p_pause);
}
@@ -249,8 +245,7 @@ void AudioStreamPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_mix_target", "mix_target"), &AudioStreamPlayer::set_mix_target);
ClassDB::bind_method(D_METHOD("get_mix_target"), &AudioStreamPlayer::get_mix_target);
- ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer::_set_playing);
- ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer::_is_active);
+ ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer::_set_playing);
ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer::set_stream_paused);
ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer::get_stream_paused);
@@ -267,7 +262,7 @@ void AudioStreamPlayer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,suffix:dB"), "set_volume_db", "get_volume_db");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused");
ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_target", PROPERTY_HINT_ENUM, "Stereo,Surround,Center"), "set_mix_target", "get_mix_target");
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index 01e3cce78b..ed7e0de0e2 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -140,7 +140,7 @@ void BaseButton::_pressed() {
void BaseButton::_toggled(bool p_pressed) {
GDVIRTUAL_CALL(_toggled, p_pressed);
toggled(p_pressed);
- emit_signal(SNAME("toggled"), p_pressed);
+ emit_signal(SceneStringName(toggled), p_pressed);
}
void BaseButton::on_action_event(Ref<InputEvent> p_event) {
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 412eb83515..e8be38e680 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -211,7 +211,13 @@ void CodeEdit::_notification(int p_what) {
tl->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
} else {
if (code_completion_options[l].default_value.get_type() == Variant::COLOR) {
- draw_rect(Rect2(Point2(code_completion_rect.position.x + code_completion_rect.size.width - icon_area_size.x, icon_area.position.y), icon_area_size), (Color)code_completion_options[l].default_value);
+ const Color color = code_completion_options[l].default_value;
+ const Rect2 rect = Rect2(Point2(code_completion_rect.position.x + code_completion_rect.size.width - icon_area_size.x, icon_area.position.y), icon_area_size);
+ if (color.a < 1.0) {
+ draw_texture_rect(theme_cache.completion_color_bg, rect, true);
+ }
+
+ draw_rect(rect, color);
}
tl->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT);
}
@@ -2771,6 +2777,7 @@ void CodeEdit::_bind_methods() {
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, CodeEdit, can_fold_code_region_icon, "can_fold_code_region");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, CodeEdit, folded_code_region_icon, "folded_code_region");
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CodeEdit, folded_eol_icon);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CodeEdit, completion_color_bg);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CodeEdit, breakpoint_color);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, CodeEdit, breakpoint_icon, "breakpoint");
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index 580435f65e..09340be035 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -245,6 +245,7 @@ private:
Ref<Texture2D> can_fold_code_region_icon;
Ref<Texture2D> folded_code_region_icon;
Ref<Texture2D> folded_eol_icon;
+ Ref<Texture2D> completion_color_bg;
Color breakpoint_color = Color(1, 1, 1);
Ref<Texture2D> breakpoint_icon = Ref<Texture2D>();
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 8a3edc25b9..c92dcbc153 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -783,7 +783,7 @@ void ColorPicker::_add_recent_preset_button(int p_size, const Color &p_color) {
recent_preset_hbc->add_child(btn_preset_new);
recent_preset_hbc->move_child(btn_preset_new, 0);
btn_preset_new->set_pressed(true);
- btn_preset_new->connect("toggled", callable_mp(this, &ColorPicker::_recent_preset_pressed).bind(btn_preset_new));
+ btn_preset_new->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_recent_preset_pressed).bind(btn_preset_new));
}
void ColorPicker::_show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_preset, Container *p_preset_container) {
@@ -2003,7 +2003,7 @@ ColorPicker::ColorPicker() {
btn_preset->set_toggle_mode(true);
btn_preset->set_focus_mode(FOCUS_NONE);
btn_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
- btn_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_preset, preset_container));
+ btn_preset->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_preset, preset_container));
real_vbox->add_child(btn_preset);
real_vbox->add_child(preset_container);
@@ -2020,7 +2020,7 @@ ColorPicker::ColorPicker() {
btn_recent_preset->set_toggle_mode(true);
btn_recent_preset->set_focus_mode(FOCUS_NONE);
btn_recent_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
- btn_recent_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_recent_preset, recent_preset_hbc));
+ btn_recent_preset->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_recent_preset, recent_preset_hbc));
real_vbox->add_child(btn_recent_preset);
real_vbox->add_child(recent_preset_hbc);
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 2003471de0..15ada0021a 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -1746,10 +1746,10 @@ void Control::_size_changed() {
// so an up to date global transform could be obtained when handling these.
_notify_transform();
+ item_rect_changed(size_changed);
if (size_changed) {
notification(NOTIFICATION_RESIZED);
}
- item_rect_changed(size_changed);
}
if (pos_changed && !size_changed) {
@@ -3606,7 +3606,7 @@ void Control::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_focus_mode", "get_focus_mode");
ADD_GROUP("Mouse", "mouse_");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass,Ignore"), "set_mouse_filter", "get_mouse_filter");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass (Propagate Up),Ignore"), "set_mouse_filter", "get_mouse_filter");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mouse_force_pass_scroll_events"), "set_force_pass_scroll_events", "is_force_pass_scroll_events");
ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,I-Beam,Pointing Hand,Cross,Wait,Busy,Drag,Can Drop,Forbidden,Vertical Resize,Horizontal Resize,Secondary Diagonal Resize,Main Diagonal Resize,Move,Vertical Split,Horizontal Split,Help"), "set_default_cursor_shape", "get_default_cursor_shape");
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 8047369ab1..1a1aa5ccb0 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -1143,7 +1143,7 @@ void FileDialog::_update_option_controls() {
CheckBox *cb = memnew(CheckBox);
cb->set_pressed(opt.default_idx);
grid_options->add_child(cb);
- cb->connect("toggled", callable_mp(this, &FileDialog::_option_changed_checkbox_toggled).bind(opt.name));
+ cb->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::_option_changed_checkbox_toggled).bind(opt.name));
selected_options[opt.name] = (bool)opt.default_idx;
} else {
OptionButton *ob = memnew(OptionButton);
@@ -1441,7 +1441,7 @@ FileDialog::FileDialog() {
show_hidden->set_toggle_mode(true);
show_hidden->set_pressed(is_showing_hidden_files());
show_hidden->set_tooltip_text(ETR("Toggle the visibility of hidden files."));
- show_hidden->connect("toggled", callable_mp(this, &FileDialog::set_show_hidden_files));
+ show_hidden->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_hidden_files));
hbc->add_child(show_hidden);
shortcuts_container = memnew(HBoxContainer);
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index 3264733548..c8b022d622 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -680,10 +680,7 @@ void MenuBar::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_menu_hidden", "menu", "hidden"), &MenuBar::set_menu_hidden);
ClassDB::bind_method(D_METHOD("is_menu_hidden", "menu"), &MenuBar::is_menu_hidden);
- // TODO: Properly handle popups when advanced GUI is disabled.
-#ifndef ADVANCED_GUI_DISABLED
ClassDB::bind_method(D_METHOD("get_menu_popup", "menu"), &MenuBar::get_menu_popup);
-#endif // ADVANCED_GUI_DISABLED
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat");
ADD_PROPERTY(PropertyInfo(Variant::INT, "start_index"), "set_start_index", "get_start_index");
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index 8c5bb1b33d..1069a752c4 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -173,10 +173,7 @@ bool MenuButton::_get(const StringName &p_name, Variant &r_ret) const {
}
void MenuButton::_bind_methods() {
- // TODO: Properly handle popups when advanced GUI is disabled.
-#ifndef ADVANCED_GUI_DISABLED
ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup);
-#endif // ADVANCED_GUI_DISABLED
ClassDB::bind_method(D_METHOD("show_popup"), &MenuButton::show_popup);
ClassDB::bind_method(D_METHOD("set_switch_on_hover", "enable"), &MenuButton::set_switch_on_hover);
ClassDB::bind_method(D_METHOD("is_switch_on_hover"), &MenuButton::is_switch_on_hover);
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 457392fb2c..0395dffad9 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -817,6 +817,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
int line_count = 0;
+ // Bottom margin for text clipping.
+ float v_limit = theme_cache.normal_style->get_margin(SIDE_BOTTOM);
Size2 ctrl_size = get_size();
// Draw text.
for (int line = 0; line < l.text_buf->get_line_count(); line++) {
@@ -824,7 +826,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
off.y += theme_cache.line_separation;
}
- if (p_ofs.y + off.y >= ctrl_size.height) {
+ if (p_ofs.y + off.y >= ctrl_size.height - v_limit) {
break;
}
@@ -1800,13 +1802,13 @@ void RichTextLabel::_notification(int p_what) {
case NOTIFICATION_RESIZED: {
_stop_thread();
- main->first_resized_line.store(0); //invalidate ALL
+ main->first_resized_line.store(0); // Invalidate all lines.
queue_redraw();
} break;
case NOTIFICATION_THEME_CHANGED: {
_stop_thread();
- main->first_invalid_font_line.store(0); //invalidate ALL
+ main->first_invalid_font_line.store(0); // Invalidate all lines.
queue_redraw();
} break;
@@ -1816,7 +1818,7 @@ void RichTextLabel::_notification(int p_what) {
set_text(text);
}
- main->first_invalid_line.store(0); //invalidate ALL
+ main->first_invalid_line.store(0); // Invalidate all lines.
queue_redraw();
} break;
@@ -1890,10 +1892,12 @@ void RichTextLabel::_notification(int p_what) {
visible_paragraph_count = 0;
visible_line_count = 0;
+ // Bottom margin for text clipping.
+ float v_limit = theme_cache.normal_style->get_margin(SIDE_BOTTOM);
// New cache draw.
Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs);
int processed_glyphs = 0;
- while (ofs.y < size.height && from_line < to_line) {
+ while (ofs.y < size.height - v_limit && from_line < to_line) {
MutexLock lock(main->lines[from_line].text_buf->get_mutex());
visible_paragraph_count++;
@@ -2528,7 +2532,7 @@ PackedFloat32Array RichTextLabel::_find_tab_stops(Item *p_item) {
item = item->parent;
}
- return PackedFloat32Array();
+ return default_tab_stops;
}
HorizontalAlignment RichTextLabel::_find_alignment(Item *p_item) {
@@ -4444,19 +4448,19 @@ void RichTextLabel::append_text(const String &p_bbcode) {
add_text(String::chr(0x00AD));
pos = brk_end + 1;
} else if (tag == "center") {
- push_paragraph(HORIZONTAL_ALIGNMENT_CENTER);
+ push_paragraph(HORIZONTAL_ALIGNMENT_CENTER, text_direction, language, st_parser, default_jst_flags, default_tab_stops);
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag == "fill") {
- push_paragraph(HORIZONTAL_ALIGNMENT_FILL);
+ push_paragraph(HORIZONTAL_ALIGNMENT_FILL, text_direction, language, st_parser, default_jst_flags, default_tab_stops);
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag == "left") {
- push_paragraph(HORIZONTAL_ALIGNMENT_LEFT);
+ push_paragraph(HORIZONTAL_ALIGNMENT_LEFT, text_direction, language, st_parser, default_jst_flags, default_tab_stops);
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag == "right") {
- push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT);
+ push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT, text_direction, language, st_parser, default_jst_flags, default_tab_stops);
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag == "ul") {
@@ -4515,8 +4519,8 @@ void RichTextLabel::append_text(const String &p_bbcode) {
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED;
- String lang;
- PackedFloat32Array tab_stops;
+ 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++) {
@@ -5734,19 +5738,89 @@ void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction)
if (text_direction != p_text_direction) {
text_direction = p_text_direction;
- main->first_invalid_line.store(0); //invalidate ALL
- _validate_line_caches();
+ if (!text.is_empty()) {
+ _apply_translation();
+ } else {
+ main->first_invalid_line.store(0); // Invalidate all lines.
+ _validate_line_caches();
+ }
+ queue_redraw();
+ }
+}
+
+Control::TextDirection RichTextLabel::get_text_direction() const {
+ return text_direction;
+}
+
+void RichTextLabel::set_horizontal_alignment(HorizontalAlignment p_alignment) {
+ ERR_FAIL_INDEX((int)p_alignment, 4);
+ _stop_thread();
+
+ if (default_alignment != p_alignment) {
+ default_alignment = p_alignment;
+ if (!text.is_empty()) {
+ _apply_translation();
+ } else {
+ main->first_invalid_line.store(0); // Invalidate all lines.
+ _validate_line_caches();
+ }
+ queue_redraw();
+ }
+}
+
+HorizontalAlignment RichTextLabel::get_horizontal_alignment() const {
+ return default_alignment;
+}
+
+void RichTextLabel::set_justification_flags(BitField<TextServer::JustificationFlag> p_flags) {
+ _stop_thread();
+
+ if (default_jst_flags != p_flags) {
+ default_jst_flags = p_flags;
+ if (!text.is_empty()) {
+ _apply_translation();
+ } else {
+ main->first_invalid_line.store(0); // Invalidate all lines.
+ _validate_line_caches();
+ }
queue_redraw();
}
}
+BitField<TextServer::JustificationFlag> RichTextLabel::get_justification_flags() const {
+ return default_jst_flags;
+}
+
+void RichTextLabel::set_tab_stops(const PackedFloat32Array &p_tab_stops) {
+ _stop_thread();
+
+ if (default_tab_stops != p_tab_stops) {
+ default_tab_stops = p_tab_stops;
+ if (!text.is_empty()) {
+ _apply_translation();
+ } else {
+ main->first_invalid_line.store(0); // Invalidate all lines.
+ _validate_line_caches();
+ }
+ queue_redraw();
+ }
+}
+
+PackedFloat32Array RichTextLabel::get_tab_stops() const {
+ return default_tab_stops;
+}
+
void RichTextLabel::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
if (st_parser != p_parser) {
_stop_thread();
st_parser = p_parser;
- main->first_invalid_line.store(0); //invalidate ALL
- _validate_line_caches();
+ if (!text.is_empty()) {
+ _apply_translation();
+ } else {
+ main->first_invalid_line.store(0); // Invalidate all lines.
+ _validate_line_caches();
+ }
queue_redraw();
}
}
@@ -5760,7 +5834,7 @@ void RichTextLabel::set_structured_text_bidi_override_options(Array p_args) {
_stop_thread();
st_args = p_args;
- main->first_invalid_line.store(0); //invalidate ALL
+ main->first_invalid_line.store(0); // Invalidate all lines.
_validate_line_caches();
queue_redraw();
}
@@ -5770,17 +5844,17 @@ Array RichTextLabel::get_structured_text_bidi_override_options() const {
return st_args;
}
-Control::TextDirection RichTextLabel::get_text_direction() const {
- return text_direction;
-}
-
void RichTextLabel::set_language(const String &p_language) {
if (language != p_language) {
_stop_thread();
language = p_language;
- main->first_invalid_line.store(0); //invalidate ALL
- _validate_line_caches();
+ if (!text.is_empty()) {
+ _apply_translation();
+ } else {
+ main->first_invalid_line.store(0); // Invalidate all lines.
+ _validate_line_caches();
+ }
queue_redraw();
}
}
@@ -5794,7 +5868,7 @@ void RichTextLabel::set_autowrap_mode(TextServer::AutowrapMode p_mode) {
_stop_thread();
autowrap_mode = p_mode;
- main->first_invalid_line = 0; //invalidate ALL
+ main->first_invalid_line = 0; // Invalidate all lines.
_validate_line_caches();
queue_redraw();
}
@@ -5820,7 +5894,7 @@ void RichTextLabel::set_visible_ratio(float p_ratio) {
}
if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
- main->first_invalid_line.store(0); // Invalidate ALL.
+ main->first_invalid_line.store(0); // Invalidate all lines..
_validate_line_caches();
}
queue_redraw();
@@ -5948,6 +6022,13 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_language", "language"), &RichTextLabel::set_language);
ClassDB::bind_method(D_METHOD("get_language"), &RichTextLabel::get_language);
+ ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &RichTextLabel::set_horizontal_alignment);
+ ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &RichTextLabel::get_horizontal_alignment);
+ ClassDB::bind_method(D_METHOD("set_justification_flags", "justification_flags"), &RichTextLabel::set_justification_flags);
+ ClassDB::bind_method(D_METHOD("get_justification_flags"), &RichTextLabel::get_justification_flags);
+ ClassDB::bind_method(D_METHOD("set_tab_stops", "tab_stops"), &RichTextLabel::set_tab_stops);
+ ClassDB::bind_method(D_METHOD("get_tab_stops"), &RichTextLabel::get_tab_stops);
+
ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &RichTextLabel::set_autowrap_mode);
ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &RichTextLabel::get_autowrap_mode);
@@ -6068,6 +6149,10 @@ void RichTextLabel::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification:1,Word Justification:2,Justify Only After Last Tab:8,Skip Last Line:32,Skip Last Line With Visible Characters:64,Do Not Skip Single Line:128"), "set_justification_flags", "get_justification_flags");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "tab_stops"), "set_tab_stops", "get_tab_stops");
+
ADD_GROUP("Markup", "");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, MAKE_RESOURCE_TYPE_HINT("RichTextEffect"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meta_underlined"), "set_meta_underline", "is_meta_underlined");
@@ -6170,7 +6255,7 @@ void RichTextLabel::set_visible_characters_behavior(TextServer::VisibleCharacter
_stop_thread();
visible_chars_behavior = p_behavior;
- main->first_invalid_line.store(0); //invalidate ALL
+ main->first_invalid_line.store(0); // Invalidate all lines.
_validate_line_caches();
queue_redraw();
}
@@ -6190,7 +6275,7 @@ void RichTextLabel::set_visible_characters(int p_visible) {
}
}
if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) {
- main->first_invalid_line.store(0); //invalidate ALL
+ main->first_invalid_line.store(0); // Invalidate all lines.
_validate_line_caches();
}
queue_redraw();
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index 9f81674454..6da13e7b2d 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -482,6 +482,7 @@ private:
HorizontalAlignment default_alignment = HORIZONTAL_ALIGNMENT_LEFT;
BitField<TextServer::JustificationFlag> default_jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE;
+ PackedFloat32Array default_tab_stops;
ItemMeta *meta_hovering = nullptr;
Variant current_meta;
@@ -808,6 +809,15 @@ public:
void set_text(const String &p_bbcode);
String get_text() const;
+ void set_horizontal_alignment(HorizontalAlignment p_alignment);
+ HorizontalAlignment get_horizontal_alignment() const;
+
+ void set_justification_flags(BitField<TextServer::JustificationFlag> p_flags);
+ BitField<TextServer::JustificationFlag> get_justification_flags() const;
+
+ void set_tab_stops(const PackedFloat32Array &p_tab_stops);
+ PackedFloat32Array get_tab_stops() const;
+
void set_text_direction(TextDirection p_text_direction);
TextDirection get_text_direction() const;
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index 2c08d36e7e..4212cd709f 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -36,7 +36,7 @@
Size2 SpinBox::get_minimum_size() const {
Size2 ms = line_edit->get_combined_minimum_size();
- ms.width += last_w;
+ ms.width += sizing_cache.buttons_block_width;
return ms;
}
@@ -128,7 +128,7 @@ void SpinBox::_range_click_timeout() {
}
}
-void SpinBox::_release_mouse() {
+void SpinBox::_release_mouse_from_drag_mode() {
if (drag.enabled) {
drag.enabled = false;
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_HIDDEN);
@@ -137,6 +137,14 @@ void SpinBox::_release_mouse() {
}
}
+void SpinBox::_mouse_exited() {
+ if (state_cache.up_button_hovered || state_cache.down_button_hovered) {
+ state_cache.up_button_hovered = false;
+ state_cache.down_button_hovered = false;
+ queue_redraw();
+ }
+}
+
void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
@@ -144,18 +152,36 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
return;
}
+ Ref<InputEventMouse> me = p_event;
Ref<InputEventMouseButton> mb = p_event;
+ Ref<InputEventMouseMotion> mm = p_event;
double step = get_custom_arrow_step() != 0.0 ? get_custom_arrow_step() : get_step();
- if (mb.is_valid() && mb->is_pressed()) {
- bool up = mb->get_position().y < (get_size().height / 2);
+ Vector2 mpos;
+ bool mouse_on_up_button = false;
+ bool mouse_on_down_button = false;
+ if (mb.is_valid() || mm.is_valid()) {
+ Rect2 up_button_rc = Rect2(sizing_cache.buttons_left, 0, sizing_cache.buttons_width, sizing_cache.button_up_height);
+ Rect2 down_button_rc = Rect2(sizing_cache.buttons_left, sizing_cache.second_button_top, sizing_cache.buttons_width, sizing_cache.button_down_height);
+
+ mpos = me->get_position();
+ mouse_on_up_button = up_button_rc.has_point(mpos);
+ mouse_on_down_button = down_button_rc.has_point(mpos);
+ }
+
+ if (mb.is_valid() && mb->is_pressed()) {
switch (mb->get_button_index()) {
case MouseButton::LEFT: {
line_edit->grab_focus();
- set_value(get_value() + (up ? step : -step));
+ if (mouse_on_up_button || mouse_on_down_button) {
+ set_value(get_value() + (mouse_on_up_button ? step : -step));
+ }
+ state_cache.up_button_pressed = mouse_on_up_button;
+ state_cache.down_button_pressed = mouse_on_down_button;
+ queue_redraw();
range_click_timer->set_wait_time(0.6);
range_click_timer->set_one_shot(true);
@@ -166,7 +192,9 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
} break;
case MouseButton::RIGHT: {
line_edit->grab_focus();
- set_value((up ? get_max() : get_min()));
+ if (mouse_on_up_button || mouse_on_down_button) {
+ set_value(mouse_on_up_button ? get_max() : get_min());
+ }
} break;
case MouseButton::WHEEL_UP: {
if (line_edit->has_focus()) {
@@ -186,14 +214,30 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
}
if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
+ if (state_cache.up_button_pressed || state_cache.down_button_pressed) {
+ state_cache.up_button_pressed = false;
+ state_cache.down_button_pressed = false;
+ queue_redraw();
+ }
+
//set_default_cursor_shape(CURSOR_ARROW);
range_click_timer->stop();
- _release_mouse();
+ _release_mouse_from_drag_mode();
drag.allowed = false;
line_edit->clear_pending_select_all_on_focus();
}
- Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ bool old_up_hovered = state_cache.up_button_hovered;
+ bool old_down_hovered = state_cache.down_button_hovered;
+
+ state_cache.up_button_hovered = mouse_on_up_button;
+ state_cache.down_button_hovered = mouse_on_down_button;
+
+ if (old_up_hovered != state_cache.up_button_hovered || old_down_hovered != state_cache.down_button_hovered) {
+ queue_redraw();
+ }
+ }
if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
if (drag.enabled) {
@@ -239,41 +283,131 @@ void SpinBox::_line_edit_focus_exit() {
_text_submitted(line_edit->get_text());
}
-inline void SpinBox::_adjust_width_for_icon(const Ref<Texture2D> &icon) {
- int w = icon->get_width();
- if ((w != last_w)) {
+inline void SpinBox::_compute_sizes() {
+ int buttons_block_wanted_width = theme_cache.buttons_width + theme_cache.field_and_buttons_separation;
+ int buttons_block_icon_enforced_width = _get_widest_button_icon_width() + theme_cache.field_and_buttons_separation;
+
+ int w = theme_cache.set_min_buttons_width_from_icons != 0 ? MAX(buttons_block_icon_enforced_width, buttons_block_wanted_width) : buttons_block_wanted_width;
+
+ if (w != sizing_cache.buttons_block_width) {
line_edit->set_offset(SIDE_LEFT, 0);
line_edit->set_offset(SIDE_RIGHT, -w);
- last_w = w;
+ sizing_cache.buttons_block_width = w;
}
+
+ Size2i size = get_size();
+
+ sizing_cache.buttons_width = w - theme_cache.field_and_buttons_separation;
+ sizing_cache.buttons_vertical_separation = CLAMP(theme_cache.buttons_vertical_separation, 0, size.height);
+ sizing_cache.buttons_left = is_layout_rtl() ? 0 : size.width - sizing_cache.buttons_width;
+ sizing_cache.button_up_height = (size.height - sizing_cache.buttons_vertical_separation) / 2;
+ sizing_cache.button_down_height = size.height - sizing_cache.button_up_height - sizing_cache.buttons_vertical_separation;
+ sizing_cache.second_button_top = size.height - sizing_cache.button_down_height;
+
+ sizing_cache.buttons_separator_top = sizing_cache.button_up_height;
+ sizing_cache.field_and_buttons_separator_left = is_layout_rtl() ? sizing_cache.buttons_width : size.width - sizing_cache.buttons_block_width;
+ sizing_cache.field_and_buttons_separator_width = theme_cache.field_and_buttons_separation;
+}
+
+inline int SpinBox::_get_widest_button_icon_width() {
+ int max = 0;
+ max = MAX(max, theme_cache.updown_icon->get_width());
+ max = MAX(max, theme_cache.up_icon->get_width());
+ max = MAX(max, theme_cache.up_hover_icon->get_width());
+ max = MAX(max, theme_cache.up_pressed_icon->get_width());
+ max = MAX(max, theme_cache.up_disabled_icon->get_width());
+ max = MAX(max, theme_cache.down_icon->get_width());
+ max = MAX(max, theme_cache.down_hover_icon->get_width());
+ max = MAX(max, theme_cache.down_pressed_icon->get_width());
+ max = MAX(max, theme_cache.down_disabled_icon->get_width());
+ return max;
}
void SpinBox::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
_update_text(true);
- _adjust_width_for_icon(theme_cache.updown_icon);
+ _compute_sizes();
RID ci = get_canvas_item();
Size2i size = get_size();
- if (is_layout_rtl()) {
- theme_cache.updown_icon->draw(ci, Point2i(0, (size.height - theme_cache.updown_icon->get_height()) / 2));
- } else {
- theme_cache.updown_icon->draw(ci, Point2i(size.width - theme_cache.updown_icon->get_width(), (size.height - theme_cache.updown_icon->get_height()) / 2));
+ Ref<StyleBox> up_stylebox = theme_cache.up_base_stylebox;
+ Ref<StyleBox> down_stylebox = theme_cache.down_base_stylebox;
+ Ref<Texture2D> up_icon = theme_cache.up_icon;
+ Ref<Texture2D> down_icon = theme_cache.down_icon;
+ Color up_icon_modulate = theme_cache.up_icon_modulate;
+ Color down_icon_modulate = theme_cache.down_icon_modulate;
+
+ bool is_fully_disabled = !is_editable();
+
+ if (state_cache.up_button_disabled || is_fully_disabled) {
+ up_stylebox = theme_cache.up_disabled_stylebox;
+ up_icon = theme_cache.up_disabled_icon;
+ up_icon_modulate = theme_cache.up_disabled_icon_modulate;
+ } else if (state_cache.up_button_pressed && !drag.enabled) {
+ up_stylebox = theme_cache.up_pressed_stylebox;
+ up_icon = theme_cache.up_pressed_icon;
+ up_icon_modulate = theme_cache.up_pressed_icon_modulate;
+ } else if (state_cache.up_button_hovered && !drag.enabled) {
+ up_stylebox = theme_cache.up_hover_stylebox;
+ up_icon = theme_cache.up_hover_icon;
+ up_icon_modulate = theme_cache.up_hover_icon_modulate;
}
+
+ if (state_cache.down_button_disabled || is_fully_disabled) {
+ down_stylebox = theme_cache.down_disabled_stylebox;
+ down_icon = theme_cache.down_disabled_icon;
+ down_icon_modulate = theme_cache.down_disabled_icon_modulate;
+ } else if (state_cache.down_button_pressed && !drag.enabled) {
+ down_stylebox = theme_cache.down_pressed_stylebox;
+ down_icon = theme_cache.down_pressed_icon;
+ down_icon_modulate = theme_cache.down_pressed_icon_modulate;
+ } else if (state_cache.down_button_hovered && !drag.enabled) {
+ down_stylebox = theme_cache.down_hover_stylebox;
+ down_icon = theme_cache.down_hover_icon;
+ down_icon_modulate = theme_cache.down_hover_icon_modulate;
+ }
+
+ int updown_icon_left = sizing_cache.buttons_left + (sizing_cache.buttons_width - theme_cache.updown_icon->get_width()) / 2;
+ int updown_icon_top = (size.height - theme_cache.updown_icon->get_height()) / 2;
+
+ // Compute center icon positions once we know which one is used.
+ int up_icon_left = sizing_cache.buttons_left + (sizing_cache.buttons_width - up_icon->get_width()) / 2;
+ int up_icon_top = (sizing_cache.button_up_height - up_icon->get_height()) / 2;
+ int down_icon_left = sizing_cache.buttons_left + (sizing_cache.buttons_width - down_icon->get_width()) / 2;
+ int down_icon_top = sizing_cache.second_button_top + (sizing_cache.button_down_height - down_icon->get_height()) / 2;
+
+ // Draw separators.
+ draw_style_box(theme_cache.up_down_buttons_separator, Rect2(sizing_cache.buttons_left, sizing_cache.buttons_separator_top, sizing_cache.buttons_width, sizing_cache.buttons_vertical_separation));
+ draw_style_box(theme_cache.field_and_buttons_separator, Rect2(sizing_cache.field_and_buttons_separator_left, 0, sizing_cache.field_and_buttons_separator_width, size.height));
+
+ // Draw buttons.
+ draw_style_box(up_stylebox, Rect2(sizing_cache.buttons_left, 0, sizing_cache.buttons_width, sizing_cache.button_up_height));
+ draw_style_box(down_stylebox, Rect2(sizing_cache.buttons_left, sizing_cache.second_button_top, sizing_cache.buttons_width, sizing_cache.button_down_height));
+
+ // Draw arrows.
+ theme_cache.updown_icon->draw(ci, Point2i(updown_icon_left, updown_icon_top));
+ draw_texture(up_icon, Point2i(up_icon_left, up_icon_top), up_icon_modulate);
+ draw_texture(down_icon, Point2i(down_icon_left, down_icon_top), down_icon_modulate);
+
+ } break;
+
+ case NOTIFICATION_MOUSE_EXIT: {
+ _mouse_exited();
} break;
case NOTIFICATION_ENTER_TREE: {
- _adjust_width_for_icon(theme_cache.updown_icon);
+ _compute_sizes();
_update_text();
+ _update_buttons_state_for_current_value();
} break;
case NOTIFICATION_VISIBILITY_CHANGED:
drag.allowed = false;
[[fallthrough]];
case NOTIFICATION_EXIT_TREE: {
- _release_mouse();
+ _release_mouse_from_drag_mode();
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
@@ -353,6 +487,7 @@ bool SpinBox::is_select_all_on_focus() const {
void SpinBox::set_editable(bool p_enabled) {
line_edit->set_editable(p_enabled);
+ queue_redraw();
}
bool SpinBox::is_editable() const {
@@ -371,6 +506,22 @@ double SpinBox::get_custom_arrow_step() const {
return custom_arrow_step;
}
+void SpinBox::_value_changed(double p_value) {
+ _update_buttons_state_for_current_value();
+}
+
+void SpinBox::_update_buttons_state_for_current_value() {
+ double value = get_value();
+ bool should_disable_up = value == get_max() && !is_greater_allowed();
+ bool should_disable_down = value == get_min() && !is_lesser_allowed();
+
+ if (state_cache.up_button_disabled != should_disable_up || state_cache.down_button_disabled != should_disable_down) {
+ state_cache.up_button_disabled = should_disable_up;
+ state_cache.down_button_disabled = should_disable_down;
+ queue_redraw();
+ }
+}
+
void SpinBox::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &SpinBox::set_horizontal_alignment);
ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &SpinBox::get_horizontal_alignment);
@@ -397,13 +548,48 @@ void SpinBox::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_arrow_step", PROPERTY_HINT_RANGE, "0,10000,0.0001,or_greater"), "set_custom_arrow_step", "get_custom_arrow_step");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_all_on_focus"), "set_select_all_on_focus", "is_select_all_on_focus");
+ BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, buttons_vertical_separation);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, field_and_buttons_separation);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, buttons_width);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, set_min_buttons_width_from_icons);
+
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, updown_icon, "updown");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_icon, "up");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_hover_icon, "up_hover");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_pressed_icon, "up_pressed");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_disabled_icon, "up_disabled");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, down_icon, "down");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, down_hover_icon, "down_hover");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, down_pressed_icon, "down_pressed");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, down_disabled_icon, "down_disabled");
+
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_base_stylebox, "up_background");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_hover_stylebox, "up_background_hovered");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_pressed_stylebox, "up_background_pressed");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_disabled_stylebox, "up_background_disabled");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, down_base_stylebox, "down_background");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, down_hover_stylebox, "down_background_hovered");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, down_pressed_stylebox, "down_background_pressed");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, down_disabled_stylebox, "down_background_disabled");
+
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, up_icon_modulate, "up_icon_modulate");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, up_hover_icon_modulate, "up_hover_icon_modulate");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, up_pressed_icon_modulate, "up_pressed_icon_modulate");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, up_disabled_icon_modulate, "up_disabled_icon_modulate");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, down_icon_modulate, "down_icon_modulate");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, down_hover_icon_modulate, "down_hover_icon_modulate");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, down_pressed_icon_modulate, "down_pressed_icon_modulate");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, down_disabled_icon_modulate, "down_disabled_icon_modulate");
+
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, field_and_buttons_separator, "field_and_buttons_separator");
+ BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_down_buttons_separator, "up_down_buttons_separator");
}
SpinBox::SpinBox() {
line_edit = memnew(LineEdit);
add_child(line_edit, false, INTERNAL_MODE_FRONT);
+ line_edit->set_theme_type_variation("SpinBoxInnerLineEdit");
line_edit->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
line_edit->set_mouse_filter(MOUSE_FILTER_PASS);
line_edit->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT);
diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h
index 4d49626d71..7c6974f6a8 100644
--- a/scene/gui/spin_box.h
+++ b/scene/gui/spin_box.h
@@ -39,12 +39,24 @@ class SpinBox : public Range {
GDCLASS(SpinBox, Range);
LineEdit *line_edit = nullptr;
- int last_w = 0;
bool update_on_text_changed = false;
+ struct SizingCache {
+ int buttons_block_width = 0;
+ int buttons_width = 0;
+ int buttons_vertical_separation = 0;
+ int buttons_left = 0;
+ int button_up_height = 0;
+ int button_down_height = 0;
+ int second_button_top = 0;
+ int buttons_separator_top = 0;
+ int field_and_buttons_separator_left = 0;
+ int field_and_buttons_separator_width = 0;
+ } sizing_cache;
+
Timer *range_click_timer = nullptr;
void _range_click_timeout();
- void _release_mouse();
+ void _release_mouse_from_drag_mode();
void _update_text(bool p_keep_line_edit = false);
void _text_submitted(const String &p_string);
@@ -65,17 +77,66 @@ class SpinBox : public Range {
double diff_y = 0.0;
} drag;
+ struct StateCache {
+ bool up_button_hovered = false;
+ bool up_button_pressed = false;
+ bool up_button_disabled = false;
+ bool down_button_hovered = false;
+ bool down_button_pressed = false;
+ bool down_button_disabled = false;
+ } state_cache;
+
void _line_edit_focus_enter();
void _line_edit_focus_exit();
- inline void _adjust_width_for_icon(const Ref<Texture2D> &icon);
+ inline void _compute_sizes();
+ inline int _get_widest_button_icon_width();
struct ThemeCache {
Ref<Texture2D> updown_icon;
+ Ref<Texture2D> up_icon;
+ Ref<Texture2D> up_hover_icon;
+ Ref<Texture2D> up_pressed_icon;
+ Ref<Texture2D> up_disabled_icon;
+ Ref<Texture2D> down_icon;
+ Ref<Texture2D> down_hover_icon;
+ Ref<Texture2D> down_pressed_icon;
+ Ref<Texture2D> down_disabled_icon;
+
+ Ref<StyleBox> up_base_stylebox;
+ Ref<StyleBox> up_hover_stylebox;
+ Ref<StyleBox> up_pressed_stylebox;
+ Ref<StyleBox> up_disabled_stylebox;
+ Ref<StyleBox> down_base_stylebox;
+ Ref<StyleBox> down_hover_stylebox;
+ Ref<StyleBox> down_pressed_stylebox;
+ Ref<StyleBox> down_disabled_stylebox;
+
+ Color up_icon_modulate;
+ Color up_hover_icon_modulate;
+ Color up_pressed_icon_modulate;
+ Color up_disabled_icon_modulate;
+ Color down_icon_modulate;
+ Color down_hover_icon_modulate;
+ Color down_pressed_icon_modulate;
+ Color down_disabled_icon_modulate;
+
+ Ref<StyleBox> field_and_buttons_separator;
+ Ref<StyleBox> up_down_buttons_separator;
+
+ int buttons_vertical_separation = 0;
+ int field_and_buttons_separation = 0;
+ int buttons_width = 0;
+ int set_min_buttons_width_from_icons = 0;
+
} theme_cache;
+ void _mouse_exited();
+ void _update_buttons_state_for_current_value();
+
protected:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
+ void _value_changed(double p_value) override;
void _notification(int p_what);
static void _bind_methods();
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 422783b01b..b6835541bf 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -2928,7 +2928,10 @@ void TextEdit::_update_ime_text() {
Size2 TextEdit::get_minimum_size() const {
Size2 size = theme_cache.style_normal->get_minimum_size();
if (fit_content_height) {
- size.y += content_height_cache;
+ size.y += content_size_cache.y;
+ }
+ if (fit_content_width) {
+ size.x += content_size_cache.x;
}
return size;
}
@@ -3098,7 +3101,7 @@ void TextEdit::apply_ime() {
insert_text_at_caret(insert_ime_text);
}
-void TextEdit::set_editable(const bool p_editable) {
+void TextEdit::set_editable(bool p_editable) {
if (editable == p_editable) {
return;
}
@@ -3223,7 +3226,7 @@ bool TextEdit::is_indent_wrapped_lines() const {
}
// User controls
-void TextEdit::set_overtype_mode_enabled(const bool p_enabled) {
+void TextEdit::set_overtype_mode_enabled(bool p_enabled) {
if (overtype_mode == p_enabled) {
return;
}
@@ -4473,7 +4476,7 @@ TextEdit::CaretType TextEdit::get_caret_type() const {
return caret_type;
}
-void TextEdit::set_caret_blink_enabled(const bool p_enabled) {
+void TextEdit::set_caret_blink_enabled(bool p_enabled) {
if (caret_blink_enabled == p_enabled) {
return;
}
@@ -4515,7 +4518,7 @@ bool TextEdit::is_drawing_caret_when_editable_disabled() const {
return draw_caret_when_editable_disabled;
}
-void TextEdit::set_move_caret_on_right_click_enabled(const bool p_enabled) {
+void TextEdit::set_move_caret_on_right_click_enabled(bool p_enabled) {
move_caret_on_right_click = p_enabled;
}
@@ -4523,7 +4526,7 @@ bool TextEdit::is_move_caret_on_right_click_enabled() const {
return move_caret_on_right_click;
}
-void TextEdit::set_caret_mid_grapheme_enabled(const bool p_enabled) {
+void TextEdit::set_caret_mid_grapheme_enabled(bool p_enabled) {
caret_mid_grapheme_enabled = p_enabled;
}
@@ -4633,7 +4636,7 @@ void TextEdit::add_caret_at_carets(bool p_below) {
for (int i = 0; i < num_carets; i++) {
const int caret_line = get_caret_line(i);
const int caret_column = get_caret_column(i);
- const bool is_selected = has_selection(i) || carets[i].last_fit_x != carets[i].selection.origin_last_fit_x;
+ bool is_selected = has_selection(i) || carets[i].last_fit_x != carets[i].selection.origin_last_fit_x;
const int selection_origin_line = get_selection_origin_line(i);
const int selection_origin_column = get_selection_origin_column(i);
const int caret_wrap_index = get_caret_wrap_index(i);
@@ -5098,7 +5101,7 @@ String TextEdit::get_word_under_caret(int p_caret) const {
}
/* Selection. */
-void TextEdit::set_selecting_enabled(const bool p_enabled) {
+void TextEdit::set_selecting_enabled(bool p_enabled) {
if (selecting_enabled == p_enabled) {
return;
}
@@ -5114,7 +5117,7 @@ bool TextEdit::is_selecting_enabled() const {
return selecting_enabled;
}
-void TextEdit::set_deselect_on_focus_loss_enabled(const bool p_enabled) {
+void TextEdit::set_deselect_on_focus_loss_enabled(bool p_enabled) {
if (deselect_on_focus_loss_enabled == p_enabled) {
return;
}
@@ -5129,7 +5132,7 @@ bool TextEdit::is_deselect_on_focus_loss_enabled() const {
return deselect_on_focus_loss_enabled;
}
-void TextEdit::set_drag_and_drop_selection_enabled(const bool p_enabled) {
+void TextEdit::set_drag_and_drop_selection_enabled(bool p_enabled) {
drag_and_drop_selection_enabled = p_enabled;
}
@@ -5689,7 +5692,7 @@ Vector<String> TextEdit::get_line_wrapped_text(int p_line) const {
/* Viewport */
// Scrolling.
-void TextEdit::set_smooth_scroll_enabled(const bool p_enabled) {
+void TextEdit::set_smooth_scroll_enabled(bool p_enabled) {
v_scroll->set_smooth_scroll_enabled(p_enabled);
smooth_scroll_enabled = p_enabled;
}
@@ -5698,7 +5701,7 @@ bool TextEdit::is_smooth_scroll_enabled() const {
return smooth_scroll_enabled;
}
-void TextEdit::set_scroll_past_end_of_file_enabled(const bool p_enabled) {
+void TextEdit::set_scroll_past_end_of_file_enabled(bool p_enabled) {
if (scroll_past_end_of_file_enabled == p_enabled) {
return;
}
@@ -5752,7 +5755,7 @@ float TextEdit::get_v_scroll_speed() const {
return v_scroll_speed;
}
-void TextEdit::set_fit_content_height_enabled(const bool p_enabled) {
+void TextEdit::set_fit_content_height_enabled(bool p_enabled) {
if (fit_content_height == p_enabled) {
return;
}
@@ -5764,6 +5767,18 @@ bool TextEdit::is_fit_content_height_enabled() const {
return fit_content_height;
}
+void TextEdit::set_fit_content_width_enabled(bool p_enabled) {
+ if (fit_content_width == p_enabled) {
+ return;
+ }
+ fit_content_width = p_enabled;
+ update_minimum_size();
+}
+
+bool TextEdit::is_fit_content_width_enabled() const {
+ return fit_content_width;
+}
+
double TextEdit::get_scroll_pos_for_line(int p_line, int p_wrap_index) const {
ERR_FAIL_INDEX_V(p_line, text.size(), 0);
ERR_FAIL_COND_V(p_wrap_index < 0, 0);
@@ -6317,7 +6332,7 @@ bool TextEdit::is_highlight_current_line_enabled() const {
return highlight_current_line;
}
-void TextEdit::set_highlight_all_occurrences(const bool p_enabled) {
+void TextEdit::set_highlight_all_occurrences(bool p_enabled) {
if (highlight_all_occurrences == p_enabled) {
return;
}
@@ -6735,6 +6750,9 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_fit_content_height_enabled", "enabled"), &TextEdit::set_fit_content_height_enabled);
ClassDB::bind_method(D_METHOD("is_fit_content_height_enabled"), &TextEdit::is_fit_content_height_enabled);
+ ClassDB::bind_method(D_METHOD("set_fit_content_width_enabled", "enabled"), &TextEdit::set_fit_content_width_enabled);
+ ClassDB::bind_method(D_METHOD("is_fit_content_width_enabled"), &TextEdit::is_fit_content_width_enabled);
+
ClassDB::bind_method(D_METHOD("get_scroll_pos_for_line", "line", "wrap_index"), &TextEdit::get_scroll_pos_for_line, DEFVAL(0));
// Visible lines.
@@ -6859,6 +6877,7 @@ void TextEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_vertical", PROPERTY_HINT_NONE, "suffix:lines"), "set_v_scroll", "get_v_scroll");
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal", PROPERTY_HINT_NONE, "suffix:px"), "set_h_scroll", "get_h_scroll");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_fit_content_height"), "set_fit_content_height_enabled", "is_fit_content_height_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_fit_content_width"), "set_fit_content_width_enabled", "is_fit_content_width_enabled");
ADD_GROUP("Minimap", "minimap_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_draw"), "set_draw_minimap", "is_drawing_minimap");
@@ -7846,8 +7865,8 @@ void TextEdit::_update_scrollbars() {
total_width += minimap_width;
}
- content_height_cache = MAX(total_rows, 1) * get_line_height();
- if (fit_content_height) {
+ content_size_cache = Vector2i(total_width + 10, MAX(total_rows, 1) * get_line_height());
+ if (fit_content_height || fit_content_width) {
update_minimum_size();
}
@@ -8054,7 +8073,7 @@ void TextEdit::_update_minimap_hover() {
const Point2 mp = get_local_mouse_pos();
const int xmargin_end = get_size().width - theme_cache.style_normal->get_margin(SIDE_RIGHT);
- const bool hovering_sidebar = mp.x > xmargin_end - minimap_width && mp.x < xmargin_end;
+ bool hovering_sidebar = mp.x > xmargin_end - minimap_width && mp.x < xmargin_end;
if (!hovering_sidebar) {
if (hovering_minimap) {
// Only redraw if the hovering status changed.
@@ -8068,7 +8087,7 @@ void TextEdit::_update_minimap_hover() {
const int row = get_minimap_line_at_pos(mp);
- const bool new_hovering_minimap = row >= get_first_visible_line() && row <= get_last_full_visible_line();
+ bool new_hovering_minimap = row >= get_first_visible_line() && row <= get_last_full_visible_line();
if (new_hovering_minimap != hovering_minimap) {
// Only redraw if the hovering status changed.
hovering_minimap = new_hovering_minimap;
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index c8cd7b0e4d..1f2fd6619a 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -505,8 +505,9 @@ private:
HScrollBar *h_scroll = nullptr;
VScrollBar *v_scroll = nullptr;
- float content_height_cache = 0.0;
+ Vector2i content_size_cache;
bool fit_content_height = false;
+ bool fit_content_width = false;
bool scroll_past_end_of_file_enabled = false;
// Smooth scrolling.
@@ -734,7 +735,7 @@ public:
void cancel_ime();
void apply_ime();
- void set_editable(const bool p_editable);
+ void set_editable(bool p_editable);
bool is_editable() const;
void set_text_direction(TextDirection p_text_direction);
@@ -755,7 +756,7 @@ public:
bool is_indent_wrapped_lines() const;
// User controls
- void set_overtype_mode_enabled(const bool p_enabled);
+ void set_overtype_mode_enabled(bool p_enabled);
bool is_overtype_mode_enabled() const;
void set_context_menu_enabled(bool p_enabled);
@@ -862,7 +863,7 @@ public:
void set_caret_type(CaretType p_type);
CaretType get_caret_type() const;
- void set_caret_blink_enabled(const bool p_enabled);
+ void set_caret_blink_enabled(bool p_enabled);
bool is_caret_blink_enabled() const;
void set_caret_blink_interval(const float p_interval);
@@ -871,10 +872,10 @@ public:
void set_draw_caret_when_editable_disabled(bool p_enable);
bool is_drawing_caret_when_editable_disabled() const;
- void set_move_caret_on_right_click_enabled(const bool p_enabled);
+ void set_move_caret_on_right_click_enabled(bool p_enabled);
bool is_move_caret_on_right_click_enabled() const;
- void set_caret_mid_grapheme_enabled(const bool p_enabled);
+ void set_caret_mid_grapheme_enabled(bool p_enabled);
bool is_caret_mid_grapheme_enabled() const;
void set_multiple_carets_enabled(bool p_enabled);
@@ -910,13 +911,13 @@ public:
String get_word_under_caret(int p_caret = -1) const;
/* Selection. */
- void set_selecting_enabled(const bool p_enabled);
+ void set_selecting_enabled(bool p_enabled);
bool is_selecting_enabled() const;
- void set_deselect_on_focus_loss_enabled(const bool p_enabled);
+ void set_deselect_on_focus_loss_enabled(bool p_enabled);
bool is_deselect_on_focus_loss_enabled() const;
- void set_drag_and_drop_selection_enabled(const bool p_enabled);
+ void set_drag_and_drop_selection_enabled(bool p_enabled);
bool is_drag_and_drop_selection_enabled() const;
void set_selection_mode(SelectionMode p_mode);
@@ -965,10 +966,10 @@ public:
/* Viewport. */
// Scrolling.
- void set_smooth_scroll_enabled(const bool p_enabled);
+ void set_smooth_scroll_enabled(bool p_enabled);
bool is_smooth_scroll_enabled() const;
- void set_scroll_past_end_of_file_enabled(const bool p_enabled);
+ void set_scroll_past_end_of_file_enabled(bool p_enabled);
bool is_scroll_past_end_of_file_enabled() const;
VScrollBar *get_v_scroll_bar() const;
@@ -983,9 +984,12 @@ public:
void set_v_scroll_speed(float p_speed);
float get_v_scroll_speed() const;
- void set_fit_content_height_enabled(const bool p_enabled);
+ void set_fit_content_height_enabled(bool p_enabled);
bool is_fit_content_height_enabled() const;
+ void set_fit_content_width_enabled(bool p_enabled);
+ bool is_fit_content_width_enabled() const;
+
double get_scroll_pos_for_line(int p_line, int p_wrap_index = 0) const;
// Visible lines.
@@ -1071,7 +1075,7 @@ public:
void set_highlight_current_line(bool p_enabled);
bool is_highlight_current_line_enabled() const;
- void set_highlight_all_occurrences(const bool p_enabled);
+ void set_highlight_all_occurrences(bool p_enabled);
bool is_highlight_all_occurrences_enabled() const;
void set_draw_control_chars(bool p_enabled);
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index d19c79b2e2..10a91c8dfc 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -773,17 +773,21 @@ TreeItem *TreeItem::create_child(int p_index) {
TreeItem *item_prev = nullptr;
TreeItem *item_next = first_child;
- int idx = 0;
- while (item_next) {
- if (idx == p_index) {
- item_next->prev = ti;
- ti->next = item_next;
- break;
- }
+ if (p_index < 0 && last_child) {
+ item_prev = last_child;
+ } else {
+ int idx = 0;
+ while (item_next) {
+ if (idx == p_index) {
+ item_next->prev = ti;
+ ti->next = item_next;
+ break;
+ }
- item_prev = item_next;
- item_next = item_next->next;
- idx++;
+ item_prev = item_next;
+ item_next = item_next->next;
+ idx++;
+ }
}
if (item_prev) {
@@ -804,6 +808,10 @@ TreeItem *TreeItem::create_child(int p_index) {
}
}
+ if (item_prev == last_child) {
+ last_child = ti;
+ }
+
ti->parent = this;
ti->parent_visible_in_tree = is_visible_in_tree();
@@ -820,17 +828,13 @@ void TreeItem::add_child(TreeItem *p_item) {
p_item->parent_visible_in_tree = is_visible_in_tree();
p_item->_handle_visibility_changed(p_item->parent_visible_in_tree);
- TreeItem *item_prev = first_child;
- while (item_prev && item_prev->next) {
- item_prev = item_prev->next;
- }
-
- if (item_prev) {
- item_prev->next = p_item;
- p_item->prev = item_prev;
+ if (last_child) {
+ last_child->next = p_item;
+ p_item->prev = last_child;
} else {
first_child = p_item;
}
+ last_child = p_item;
if (!children_cache.is_empty()) {
children_cache.append(p_item);
@@ -910,13 +914,8 @@ TreeItem *TreeItem::_get_prev_in_tree(bool p_wrap, bool p_include_invisible) {
}
} else {
current = prev_item;
- while ((!current->collapsed || p_include_invisible) && current->first_child) {
- //go to the very end
-
- current = current->first_child;
- while (current->next) {
- current = current->next;
- }
+ while ((!current->collapsed || p_include_invisible) && current->last_child) {
+ current = current->last_child;
}
}
@@ -1037,6 +1036,8 @@ void TreeItem::clear_children() {
}
first_child = nullptr;
+ last_child = nullptr;
+ children_cache.clear();
};
int TreeItem::get_index() {
@@ -1141,6 +1142,7 @@ void TreeItem::move_after(TreeItem *p_item) {
if (next) {
parent->children_cache.clear();
} else {
+ parent->last_child = this;
// If the cache is empty, it has not been built but there
// are items in the tree (note p_item != nullptr,) so we cannot update it.
if (!parent->children_cache.is_empty()) {
@@ -4468,15 +4470,8 @@ TreeItem *Tree::get_root() const {
TreeItem *Tree::get_last_item() const {
TreeItem *last = root;
-
- while (last) {
- if (last->next) {
- last = last->next;
- } else if (last->first_child && !last->collapsed) {
- last = last->first_child;
- } else {
- break;
- }
+ while (last && last->last_child && !last->collapsed) {
+ last = last->last_child;
}
return last;
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 3200459b5a..4518708685 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -136,6 +136,7 @@ private:
TreeItem *prev = nullptr; // previous in list
TreeItem *next = nullptr; // next in list
TreeItem *first_child = nullptr;
+ TreeItem *last_child = nullptr;
Vector<TreeItem *> children_cache;
bool is_root = false; // for tree root
@@ -177,6 +178,9 @@ private:
if (parent->first_child == this) {
parent->first_child = next;
}
+ if (parent->last_child == this) {
+ parent->last_child = prev;
+ }
}
}
diff --git a/scene/main/node.h b/scene/main/node.h
index 2f6372dad5..e412459105 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -657,7 +657,7 @@ public:
return binds;
}
- void replace_by(Node *p_node, bool p_keep_data = false);
+ void replace_by(Node *p_node, bool p_keep_groups = false);
void set_process_mode(ProcessMode p_mode);
ProcessMode get_process_mode() const;
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 192427f8a6..a370ef2d18 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -884,6 +884,10 @@ void Viewport::_process_picking() {
}
#ifndef _3D_DISABLED
+ if (physics_object_picking_first_only && is_input_handled()) {
+ continue;
+ }
+
CollisionObject3D *capture_object = nullptr;
if (physics_object_capture.is_valid()) {
capture_object = Object::cast_to<CollisionObject3D>(ObjectDB::get_instance(physics_object_capture));
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index aa8ff75c6a..76678e609a 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -402,8 +402,6 @@ void register_scene_types() {
GDREGISTER_CLASS(VSlider);
GDREGISTER_CLASS(Popup);
GDREGISTER_CLASS(PopupPanel);
- GDREGISTER_CLASS(MenuBar);
- GDREGISTER_CLASS(MenuButton);
GDREGISTER_CLASS(CheckBox);
GDREGISTER_CLASS(CheckButton);
GDREGISTER_CLASS(LinkButton);
@@ -458,6 +456,8 @@ void register_scene_types() {
GDREGISTER_CLASS(CodeHighlighter);
GDREGISTER_ABSTRACT_CLASS(TreeItem);
+ GDREGISTER_CLASS(MenuBar);
+ GDREGISTER_CLASS(MenuButton);
GDREGISTER_CLASS(OptionButton);
GDREGISTER_CLASS(SpinBox);
GDREGISTER_CLASS(ColorPicker);
diff --git a/scene/resources/2d/navigation_polygon.h b/scene/resources/2d/navigation_polygon.h
index 86bda47ace..ed2c606c55 100644
--- a/scene/resources/2d/navigation_polygon.h
+++ b/scene/resources/2d/navigation_polygon.h
@@ -33,6 +33,7 @@
#include "scene/2d/node_2d.h"
#include "scene/resources/navigation_mesh.h"
+#include "servers/navigation/navigation_globals.h"
class NavigationPolygon : public Resource {
GDCLASS(NavigationPolygon, Resource);
@@ -50,7 +51,7 @@ class NavigationPolygon : public Resource {
// Navigation mesh
Ref<NavigationMesh> navigation_mesh;
- real_t cell_size = 1.0f; // Must match ProjectSettings default 2D cell_size.
+ real_t cell_size = NavigationDefaults2D::navmesh_cell_size;
real_t border_size = 0.0f;
Rect2 baking_rect;
diff --git a/scene/resources/3d/fog_material.cpp b/scene/resources/3d/fog_material.cpp
index 5e4f1970ee..92246b50db 100644
--- a/scene/resources/3d/fog_material.cpp
+++ b/scene/resources/3d/fog_material.cpp
@@ -138,7 +138,7 @@ void FogMaterial::cleanup_shader() {
}
void FogMaterial::_update_shader() {
- shader_mutex.lock();
+ MutexLock shader_lock(shader_mutex);
if (shader.is_null()) {
shader = RS::get_singleton()->shader_create();
@@ -165,7 +165,6 @@ void fog() {
}
)");
}
- shader_mutex.unlock();
}
FogMaterial::FogMaterial() {
diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp
index b0633c06b9..4f4c485db3 100644
--- a/scene/resources/3d/importer_mesh.cpp
+++ b/scene/resources/3d/importer_mesh.cpp
@@ -1418,5 +1418,5 @@ void ImporterMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_lightmap_size_hint", "size"), &ImporterMesh::set_lightmap_size_hint);
ClassDB::bind_method(D_METHOD("get_lightmap_size_hint"), &ImporterMesh::get_lightmap_size_hint);
- ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data");
+ ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
}
diff --git a/scene/resources/3d/sky_material.cpp b/scene/resources/3d/sky_material.cpp
index 640261d615..c470db5d7f 100644
--- a/scene/resources/3d/sky_material.cpp
+++ b/scene/resources/3d/sky_material.cpp
@@ -269,7 +269,7 @@ void ProceduralSkyMaterial::cleanup_shader() {
}
void ProceduralSkyMaterial::_update_shader() {
- shader_mutex.lock();
+ MutexLock shader_lock(shader_mutex);
if (shader_cache[0].is_null()) {
for (int i = 0; i < 2; i++) {
shader_cache[i] = RS::get_singleton()->shader_create();
@@ -354,7 +354,6 @@ void sky() {
i ? "render_mode use_debanding;" : ""));
}
}
- shader_mutex.unlock();
}
ProceduralSkyMaterial::ProceduralSkyMaterial() {
@@ -463,7 +462,7 @@ void PanoramaSkyMaterial::cleanup_shader() {
}
void PanoramaSkyMaterial::_update_shader() {
- shader_mutex.lock();
+ MutexLock shader_lock(shader_mutex);
if (shader_cache[0].is_null()) {
for (int i = 0; i < 2; i++) {
shader_cache[i] = RS::get_singleton()->shader_create();
@@ -484,8 +483,6 @@ void sky() {
i ? "filter_linear" : "filter_nearest"));
}
}
-
- shader_mutex.unlock();
}
PanoramaSkyMaterial::PanoramaSkyMaterial() {
@@ -692,7 +689,7 @@ void PhysicalSkyMaterial::cleanup_shader() {
}
void PhysicalSkyMaterial::_update_shader() {
- shader_mutex.lock();
+ MutexLock shader_lock(shader_mutex);
if (shader_cache[0].is_null()) {
for (int i = 0; i < 2; i++) {
shader_cache[i] = RS::get_singleton()->shader_create();
@@ -785,8 +782,6 @@ void sky() {
i ? "render_mode use_debanding;" : ""));
}
}
-
- shader_mutex.unlock();
}
PhysicalSkyMaterial::PhysicalSkyMaterial() {
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index 9e6d34959a..0c29790ea4 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -45,7 +45,7 @@ public:
static inline String PARAMETERS_BASE_PATH = "parameters/";
- enum TrackType {
+ enum TrackType : uint8_t {
TYPE_VALUE, // Set a value in a property, can be interpolated.
TYPE_POSITION_3D, // Position 3D track, can be compressed.
TYPE_ROTATION_3D, // Rotation 3D track, can be compressed.
@@ -57,7 +57,7 @@ public:
TYPE_ANIMATION,
};
- enum InterpolationType {
+ enum InterpolationType : uint8_t {
INTERPOLATION_NEAREST,
INTERPOLATION_LINEAR,
INTERPOLATION_CUBIC,
@@ -65,26 +65,26 @@ public:
INTERPOLATION_CUBIC_ANGLE,
};
- enum UpdateMode {
+ enum UpdateMode : uint8_t {
UPDATE_CONTINUOUS,
UPDATE_DISCRETE,
UPDATE_CAPTURE,
};
- enum LoopMode {
+ enum LoopMode : uint8_t {
LOOP_NONE,
LOOP_LINEAR,
LOOP_PINGPONG,
};
// LoopedFlag is used in Animataion to "process the keys at both ends correct".
- enum LoopedFlag {
+ enum LoopedFlag : uint8_t {
LOOPED_FLAG_NONE,
LOOPED_FLAG_END,
LOOPED_FLAG_START,
};
- enum FindMode {
+ enum FindMode : uint8_t {
FIND_MODE_NEAREST,
FIND_MODE_APPROX,
FIND_MODE_EXACT,
@@ -104,7 +104,6 @@ public:
};
#endif // TOOLS_ENABLED
-private:
struct Track {
TrackType type = TrackType::TYPE_ANIMATION;
InterpolationType interpolation = INTERPOLATION_LINEAR;
@@ -117,6 +116,7 @@ private:
virtual ~Track() {}
};
+private:
struct Key {
real_t transition = 1.0;
double time = 0.0; // Time in secs.
@@ -396,6 +396,10 @@ public:
int add_track(TrackType p_type, int p_at_pos = -1);
void remove_track(int p_track);
+ _FORCE_INLINE_ const Vector<Track *> get_tracks() {
+ return tracks;
+ }
+
bool is_capture_included() const;
int get_track_count() const;
diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp
index de6a069567..08ebacc2b3 100644
--- a/scene/resources/audio_stream_wav.cpp
+++ b/scene/resources/audio_stream_wav.cpp
@@ -179,17 +179,17 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst,
if (pos != p_qoa->cache_pos) { // Prevents triple decoding on lower mix rates.
for (int i = 0; i < 2; i++) {
// Sign operations prevent triple decoding on backward loops, maxing prevents pop.
- uint32_t interp_pos = MIN(pos + (i * sign) + (sign < 0), p_qoa->desc->samples - 1);
+ uint32_t interp_pos = MIN(pos + (i * sign) + (sign < 0), p_qoa->desc.samples - 1);
uint32_t new_data_ofs = 8 + interp_pos / QOA_FRAME_LEN * p_qoa->frame_len;
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);
+ qoa_decode_frame(src_ptr, p_qoa->frame_len, &p_qoa->desc, p_qoa->dec, &p_qoa->dec_len);
}
- uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc->channels;
+ uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc.channels;
if ((sign > 0 && i == 0) || (sign < 0 && i == 1)) {
final = p_qoa->dec[dec_idx];
@@ -286,7 +286,7 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_
len *= 2;
break;
case AudioStreamWAV::FORMAT_QOA:
- len = qoa.desc->samples * qoa.desc->channels;
+ len = qoa.desc.samples * qoa.desc.channels;
break;
}
@@ -484,10 +484,6 @@ void AudioStreamPlaybackWAV::set_sample_playback(const Ref<AudioSamplePlayback>
AudioStreamPlaybackWAV::AudioStreamPlaybackWAV() {}
AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() {
- if (qoa.desc) {
- memfree(qoa.desc);
- }
-
if (qoa.dec) {
memfree(qoa.dec);
}
@@ -557,7 +553,7 @@ double AudioStreamWAV::get_length() const {
len *= 2;
break;
case AudioStreamWAV::FORMAT_QOA:
- qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } };
+ qoa_desc desc = {};
qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, &desc);
len = desc.samples * desc.channels;
break;
@@ -697,12 +693,11 @@ Ref<AudioStreamPlayback> AudioStreamWAV::instantiate_playback() {
sample->base = Ref<AudioStreamWAV>(this);
if (format == AudioStreamWAV::FORMAT_QOA) {
- sample->qoa.desc = (qoa_desc *)memalloc(sizeof(qoa_desc));
- uint32_t ffp = qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, sample->qoa.desc);
+ uint32_t ffp = qoa_decode_header((uint8_t *)data + 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.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);
}
@@ -765,7 +760,7 @@ void AudioStreamWAV::_bind_methods() {
ClassDB::bind_method(D_METHOD("save_to_wav", "path"), &AudioStreamWAV::save_to_wav);
ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_data", "get_data");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM,QOA"), "set_format", "get_format");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA ADPCM,Quite OK Audio"), "set_format", "get_format");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong,Backward"), "set_loop_mode", "get_loop_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_begin"), "set_loop_begin", "get_loop_begin");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_end"), "set_loop_end", "get_loop_end");
diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h
index 806db675b6..47aa10e790 100644
--- a/scene/resources/audio_stream_wav.h
+++ b/scene/resources/audio_stream_wav.h
@@ -59,7 +59,7 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback {
} ima_adpcm[2];
struct QOA_State {
- qoa_desc *desc = nullptr;
+ qoa_desc desc = {};
uint32_t data_ofs = 0;
uint32_t frame_len = 0;
int16_t *dec = nullptr;
diff --git a/scene/resources/navigation_mesh.h b/scene/resources/navigation_mesh.h
index 741cea0791..1b3db5bac2 100644
--- a/scene/resources/navigation_mesh.h
+++ b/scene/resources/navigation_mesh.h
@@ -33,6 +33,7 @@
#include "core/os/rw_lock.h"
#include "scene/resources/mesh.h"
+#include "servers/navigation/navigation_globals.h"
class NavigationMesh : public Resource {
GDCLASS(NavigationMesh, Resource);
@@ -77,8 +78,8 @@ public:
};
protected:
- float cell_size = 0.25f; // Must match ProjectSettings default 3D cell_size and NavigationServer NavMap cell_size.
- float cell_height = 0.25f; // Must match ProjectSettings default 3D cell_height and NavigationServer NavMap cell_height.
+ float cell_size = NavigationDefaults3D::navmesh_cell_size;
+ float cell_height = NavigationDefaults3D::navmesh_cell_height;
float border_size = 0.0f;
float agent_height = 1.5f;
float agent_radius = 0.5f;
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index 900629f5f8..27db65bb1a 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -1880,6 +1880,16 @@ Vector<NodePath> SceneState::get_editable_instances() const {
return editable_instances;
}
+Ref<Resource> SceneState::get_sub_resource(const String &p_path) {
+ for (const Variant &v : variants) {
+ const Ref<Resource> &res = v;
+ if (res.is_valid() && res->get_path() == p_path) {
+ return res;
+ }
+ }
+ return Ref<Resource>();
+}
+
//add
int SceneState::add_name(const StringName &p_name) {
@@ -2199,7 +2209,7 @@ void PackedScene::_bind_methods() {
ClassDB::bind_method(D_METHOD("_get_bundled_scene"), &PackedScene::_get_bundled_scene);
ClassDB::bind_method(D_METHOD("get_state"), &PackedScene::get_state);
- ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_bundled"), "_set_bundled_scene", "_get_bundled_scene");
+ ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_bundled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL), "_set_bundled_scene", "_get_bundled_scene");
BIND_ENUM_CONSTANT(GEN_EDIT_STATE_DISABLED);
BIND_ENUM_CONSTANT(GEN_EDIT_STATE_INSTANCE);
diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h
index e26b9f7b90..d27def1760 100644
--- a/scene/resources/packed_scene.h
+++ b/scene/resources/packed_scene.h
@@ -195,6 +195,7 @@ public:
bool has_connection(const NodePath &p_node_from, const StringName &p_signal, const NodePath &p_node_to, const StringName &p_method, bool p_no_inheritance = false);
Vector<NodePath> get_editable_instances() const;
+ Ref<Resource> get_sub_resource(const String &p_path);
//build API
diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp
index ee986f5820..8cfe4c92b7 100644
--- a/scene/resources/particle_process_material.cpp
+++ b/scene/resources/particle_process_material.cpp
@@ -110,6 +110,7 @@ void ParticleProcessMaterial::init_shaders() {
shader_names->emission_ring_height = "emission_ring_height";
shader_names->emission_ring_radius = "emission_ring_radius";
shader_names->emission_ring_inner_radius = "emission_ring_inner_radius";
+ shader_names->emission_ring_cone_angle = "emission_ring_cone_angle";
shader_names->emission_shape_offset = "emission_shape_offset";
shader_names->emission_shape_scale = "emission_shape_scale";
@@ -269,6 +270,7 @@ void ParticleProcessMaterial::_update_shader() {
code += "uniform float " + shader_names->emission_ring_height + ";\n";
code += "uniform float " + shader_names->emission_ring_radius + ";\n";
code += "uniform float " + shader_names->emission_ring_inner_radius + ";\n";
+ code += "uniform float " + shader_names->emission_ring_cone_angle + ";\n";
} break;
case EMISSION_SHAPE_MAX: { // Max value for validity check.
break;
@@ -643,8 +645,14 @@ void ParticleProcessMaterial::_update_shader() {
code += " pos = texelFetch(emission_texture_points, emission_tex_ofs, 0).xyz;\n";
}
if (emission_shape == EMISSION_SHAPE_RING) {
+ code += " float radius_clamped = max(0.001, emission_ring_radius);\n";
+ code += " float top_radius = max(radius_clamped - tan(radians(90.0 - emission_ring_cone_angle)) * emission_ring_height, 0.0);\n";
+ code += " float y_pos = rand_from_seed(alt_seed);\n";
+ code += " float skew = max(min(radius_clamped, top_radius) / max(radius_clamped, top_radius), 0.5);\n";
+ code += " y_pos = radius_clamped < top_radius ? pow(y_pos, skew) : 1.0 - pow(y_pos, skew);\n";
code += " float ring_spawn_angle = rand_from_seed(alt_seed) * 2.0 * pi;\n";
- code += " float ring_random_radius = sqrt(rand_from_seed(alt_seed) * (emission_ring_radius * emission_ring_radius - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);\n";
+ code += " float ring_random_radius = sqrt(rand_from_seed(alt_seed) * (radius_clamped * radius_clamped - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);\n";
+ code += " ring_random_radius = mix(ring_random_radius, ring_random_radius * (top_radius / radius_clamped), y_pos);\n";
code += " vec3 axis = emission_ring_axis == vec3(0.0) ? vec3(0.0, 0.0, 1.0) : normalize(emission_ring_axis);\n";
code += " vec3 ortho_axis = vec3(0.0);\n";
code += " if (abs(axis) == vec3(1.0, 0.0, 0.0)) {\n";
@@ -662,7 +670,7 @@ void ParticleProcessMaterial::_update_shader() {
code += " vec3(axis.z * axis.x * oc - axis.y * s, axis.z * axis.y * oc + axis.x * s, c + axis.z * axis.z * oc)\n";
code += " ) * ortho_axis;\n";
code += " ortho_axis = normalize(ortho_axis);\n";
- code += " pos = ortho_axis * ring_random_radius + (rand_from_seed(alt_seed) * emission_ring_height - emission_ring_height / 2.0) * axis;\n";
+ code += " pos = ortho_axis * ring_random_radius + (y_pos * emission_ring_height - emission_ring_height / 2.0) * axis;\n";
}
code += " }\n";
code += " return pos * emission_shape_scale + emission_shape_offset;\n";
@@ -1615,6 +1623,11 @@ void ParticleProcessMaterial::set_emission_ring_inner_radius(real_t p_radius) {
RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_inner_radius, p_radius);
}
+void ParticleProcessMaterial::set_emission_ring_cone_angle(real_t p_angle) {
+ emission_ring_cone_angle = p_angle;
+ RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_cone_angle, p_angle);
+}
+
void ParticleProcessMaterial::set_inherit_velocity_ratio(double p_ratio) {
inherit_emitter_velocity_ratio = p_ratio;
RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->inherit_emitter_velocity_ratio, p_ratio);
@@ -1664,6 +1677,10 @@ real_t ParticleProcessMaterial::get_emission_ring_inner_radius() const {
return emission_ring_inner_radius;
}
+real_t ParticleProcessMaterial::get_emission_ring_cone_angle() const {
+ return emission_ring_cone_angle;
+}
+
void ParticleProcessMaterial::set_emission_shape_offset(const Vector3 &p_emission_shape_offset) {
emission_shape_offset = p_emission_shape_offset;
RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_shape_offset, p_emission_shape_offset);
@@ -2015,6 +2032,9 @@ void ParticleProcessMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &ParticleProcessMaterial::set_emission_ring_inner_radius);
ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &ParticleProcessMaterial::get_emission_ring_inner_radius);
+ ClassDB::bind_method(D_METHOD("set_emission_ring_cone_angle", "cone_angle"), &ParticleProcessMaterial::set_emission_ring_cone_angle);
+ ClassDB::bind_method(D_METHOD("get_emission_ring_cone_angle"), &ParticleProcessMaterial::get_emission_ring_cone_angle);
+
ClassDB::bind_method(D_METHOD("set_emission_shape_offset", "emission_shape_offset"), &ParticleProcessMaterial::set_emission_shape_offset);
ClassDB::bind_method(D_METHOD("get_emission_shape_offset"), &ParticleProcessMaterial::get_emission_shape_offset);
@@ -2096,9 +2116,10 @@ void ParticleProcessMaterial::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_color_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_emission_color_texture", "get_emission_color_texture");
ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_point_count", PROPERTY_HINT_RANGE, "0,1000000,1"), "set_emission_point_count", "get_emission_point_count");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_ring_axis"), "set_emission_ring_axis", "get_emission_ring_axis");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_height", "get_emission_ring_height");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_radius", "get_emission_ring_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_cone_angle", PROPERTY_HINT_RANGE, "0,90,0.01,degrees"), "set_emission_ring_cone_angle", "get_emission_ring_cone_angle");
ADD_SUBGROUP("Angle", "");
ADD_MIN_MAX_PROPERTY("angle", "-720,720,0.1,or_less,or_greater,degrees", PARAM_ANGLE);
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGLE);
@@ -2276,6 +2297,7 @@ ParticleProcessMaterial::ParticleProcessMaterial() :
set_emission_ring_height(1);
set_emission_ring_radius(1);
set_emission_ring_inner_radius(0);
+ set_emission_ring_cone_angle(90);
set_emission_shape_offset(Vector3(0.0, 0.0, 0.0));
set_emission_shape_scale(Vector3(1.0, 1.0, 1.0));
diff --git a/scene/resources/particle_process_material.h b/scene/resources/particle_process_material.h
index 25046b51cd..12e3fbb64e 100644
--- a/scene/resources/particle_process_material.h
+++ b/scene/resources/particle_process_material.h
@@ -259,6 +259,7 @@ private:
StringName emission_ring_height;
StringName emission_ring_radius;
StringName emission_ring_inner_radius;
+ StringName emission_ring_cone_angle;
StringName emission_shape_offset;
StringName emission_shape_scale;
@@ -325,6 +326,7 @@ private:
real_t emission_ring_height = 0.0f;
real_t emission_ring_radius = 0.0f;
real_t emission_ring_inner_radius = 0.0f;
+ real_t emission_ring_cone_angle = 0.0f;
int emission_point_count = 1;
Vector3 emission_shape_offset;
Vector3 emission_shape_scale;
@@ -417,6 +419,7 @@ public:
void set_emission_ring_height(real_t p_height);
void set_emission_ring_radius(real_t p_radius);
void set_emission_ring_inner_radius(real_t p_radius);
+ void set_emission_ring_cone_angle(real_t p_angle);
void set_emission_point_count(int p_count);
EmissionShape get_emission_shape() const;
@@ -429,6 +432,7 @@ public:
real_t get_emission_ring_height() const;
real_t get_emission_ring_radius() const;
real_t get_emission_ring_inner_radius() const;
+ real_t get_emission_ring_cone_angle() const;
int get_emission_point_count() const;
void set_turbulence_enabled(bool p_turbulence_enabled);
diff --git a/scene/resources/portable_compressed_texture.cpp b/scene/resources/portable_compressed_texture.cpp
index 002db30379..06b5ec6d5a 100644
--- a/scene/resources/portable_compressed_texture.cpp
+++ b/scene/resources/portable_compressed_texture.cpp
@@ -345,7 +345,7 @@ void PortableCompressedTexture2D::_bind_methods() {
ClassDB::bind_static_method("PortableCompressedTexture2D", D_METHOD("set_keep_all_compressed_buffers", "keep"), &PortableCompressedTexture2D::set_keep_all_compressed_buffers);
ClassDB::bind_static_method("PortableCompressedTexture2D", D_METHOD("is_keeping_all_compressed_buffers"), &PortableCompressedTexture2D::is_keeping_all_compressed_buffers);
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size_override", PROPERTY_HINT_NONE, "suffix:px"), "set_size_override", "get_size_override");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_compressed_buffer"), "set_keep_compressed_buffer", "is_keeping_compressed_buffer");
diff --git a/scene/resources/shader.compat.inc b/scene/resources/shader.compat.inc
new file mode 100644
index 0000000000..b68020605f
--- /dev/null
+++ b/scene/resources/shader.compat.inc
@@ -0,0 +1,46 @@
+/**************************************************************************/
+/* shader.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 Shader::_set_default_texture_parameter_bind_compat_95126(const StringName &p_name, const Ref<Texture2D> &p_texture, int p_index) {
+ set_default_texture_parameter(p_name, p_texture, p_index);
+}
+
+Ref<Texture2D> Shader::_get_default_texture_parameter_bind_compat_95126(const StringName &p_name, int p_index) const {
+ return get_default_texture_parameter(p_name, p_index);
+}
+
+void Shader::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("set_default_texture_parameter", "name", "texture", "index"), &Shader::_set_default_texture_parameter_bind_compat_95126, DEFVAL(0));
+ ClassDB::bind_compatibility_method(D_METHOD("get_default_texture_parameter", "name", "index"), &Shader::_get_default_texture_parameter_bind_compat_95126, DEFVAL(0));
+}
+
+#endif // DISABLE_DEPRECATED
diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp
index dfe5bd4a47..f343229cd8 100644
--- a/scene/resources/shader.cpp
+++ b/scene/resources/shader.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "shader.h"
+#include "shader.compat.inc"
#include "core/io/file_access.h"
#include "servers/rendering/shader_language.h"
@@ -185,10 +186,10 @@ RID Shader::get_rid() const {
return shader;
}
-void Shader::set_default_texture_parameter(const StringName &p_name, const Ref<Texture2D> &p_texture, int p_index) {
+void Shader::set_default_texture_parameter(const StringName &p_name, const Ref<Texture> &p_texture, int p_index) {
if (p_texture.is_valid()) {
if (!default_textures.has(p_name)) {
- default_textures[p_name] = HashMap<int, Ref<Texture2D>>();
+ default_textures[p_name] = HashMap<int, Ref<Texture>>();
}
default_textures[p_name][p_index] = p_texture;
RS::get_singleton()->shader_set_default_texture_parameter(shader, p_name, p_texture->get_rid(), p_index);
@@ -206,7 +207,7 @@ void Shader::set_default_texture_parameter(const StringName &p_name, const Ref<T
emit_changed();
}
-Ref<Texture2D> Shader::get_default_texture_parameter(const StringName &p_name, int p_index) const {
+Ref<Texture> Shader::get_default_texture_parameter(const StringName &p_name, int p_index) const {
if (default_textures.has(p_name) && default_textures[p_name].has(p_index)) {
return default_textures[p_name][p_index];
}
@@ -214,7 +215,7 @@ Ref<Texture2D> Shader::get_default_texture_parameter(const StringName &p_name, i
}
void Shader::get_default_texture_parameter_list(List<StringName> *r_textures) const {
- for (const KeyValue<StringName, HashMap<int, Ref<Texture2D>>> &E : default_textures) {
+ for (const KeyValue<StringName, HashMap<int, Ref<Texture>>> &E : default_textures) {
r_textures->push_back(E.key);
}
}
diff --git a/scene/resources/shader.h b/scene/resources/shader.h
index 921143c219..682fbd7ea6 100644
--- a/scene/resources/shader.h
+++ b/scene/resources/shader.h
@@ -58,7 +58,7 @@ private:
String code;
String include_path;
- HashMap<StringName, HashMap<int, Ref<Texture2D>>> default_textures;
+ HashMap<StringName, HashMap<int, Ref<Texture>>> default_textures;
void _dependency_changed();
void _recompile();
@@ -66,6 +66,12 @@ private:
Array _get_shader_uniform_list(bool p_get_groups = false);
protected:
+#ifndef DISABLE_DEPRECATED
+ void _set_default_texture_parameter_bind_compat_95126(const StringName &p_name, const Ref<Texture2D> &p_texture, int p_index = 0);
+ Ref<Texture2D> _get_default_texture_parameter_bind_compat_95126(const StringName &p_name, int p_index = 0) const;
+ static void _bind_compatibility_methods();
+#endif // DISABLE_DEPRECATED
+
static void _bind_methods();
public:
@@ -80,8 +86,8 @@ public:
void get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_groups = false) const;
- void set_default_texture_parameter(const StringName &p_name, const Ref<Texture2D> &p_texture, int p_index = 0);
- Ref<Texture2D> get_default_texture_parameter(const StringName &p_name, int p_index = 0) const;
+ void set_default_texture_parameter(const StringName &p_name, const Ref<Texture> &p_texture, int p_index = 0);
+ Ref<Texture> get_default_texture_parameter(const StringName &p_name, int p_index = 0) const;
void get_default_texture_parameter_list(List<StringName> *r_textures) const;
virtual bool is_text_shader() const;
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index a144c5ba83..d0e55f4065 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -833,7 +833,7 @@ VisualShader::Type VisualShader::get_shader_type() const {
}
void VisualShader::add_varying(const String &p_name, VaryingMode p_mode, VaryingType p_type) {
- ERR_FAIL_COND(!p_name.is_valid_identifier());
+ ERR_FAIL_COND(!p_name.is_valid_ascii_identifier());
ERR_FAIL_INDEX((int)p_mode, (int)VARYING_MODE_MAX);
ERR_FAIL_INDEX((int)p_type, (int)VARYING_TYPE_MAX);
ERR_FAIL_COND(varyings.has(p_name));
@@ -2953,7 +2953,7 @@ void VisualShader::_update_shader() const {
const_cast<VisualShader *>(this)->set_code(final_code);
for (int i = 0; i < default_tex_params.size(); i++) {
int j = 0;
- for (List<Ref<Texture2D>>::ConstIterator itr = default_tex_params[i].params.begin(); itr != default_tex_params[i].params.end(); ++itr, ++j) {
+ for (List<Ref<Texture>>::ConstIterator itr = default_tex_params[i].params.begin(); itr != default_tex_params[i].params.end(); ++itr, ++j) {
const_cast<VisualShader *>(this)->set_default_texture_parameter(default_tex_params[i].name, *itr, j);
}
}
@@ -3082,6 +3082,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = {
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "camera_direction_world", "CAMERA_DIRECTION_WORLD" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "camera_position_world", "CAMERA_POSITION_WORLD" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "camera_visible_layers", "CAMERA_VISIBLE_LAYERS" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "clip_space_far", "CLIP_SPACE_FAR" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom0", "CUSTOM0" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_4D, "custom1", "CUSTOM1" },
@@ -3119,6 +3120,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = {
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "camera_direction_world", "CAMERA_DIRECTION_WORLD" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "camera_position_world", "CAMERA_POSITION_WORLD" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR_UINT, "camera_visible_layers", "CAMERA_VISIBLE_LAYERS" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "clip_space_far", "CLIP_SPACE_FAR" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "COLOR" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "exposure", "EXPOSURE" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "eye_offset", "EYE_OFFSET" },
@@ -3150,6 +3152,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = {
{ Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "albedo", "ALBEDO" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "attenuation", "ATTENUATION" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "backlight", "BACKLIGHT" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "clip_space_far", "CLIP_SPACE_FAR" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "diffuse", "DIFFUSE_LIGHT" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "exposure", "EXPOSURE" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "fragcoord", "FRAGCOORD" },
@@ -4571,7 +4574,7 @@ String VisualShaderNodeGroupBase::get_outputs() const {
}
bool VisualShaderNodeGroupBase::is_valid_port_name(const String &p_name) const {
- if (!p_name.is_valid_identifier()) {
+ if (!p_name.is_valid_ascii_identifier()) {
return false;
}
for (int i = 0; i < get_input_port_count(); i++) {
diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h
index 2b213948de..8ec52fcaaa 100644
--- a/scene/resources/visual_shader.h
+++ b/scene/resources/visual_shader.h
@@ -66,7 +66,7 @@ public:
struct DefaultTextureParam {
StringName name;
- List<Ref<Texture2D>> params;
+ List<Ref<Texture>> params;
};
enum VaryingMode {
diff --git a/scene/resources/visual_shader_nodes.compat.inc b/scene/resources/visual_shader_nodes.compat.inc
new file mode 100644
index 0000000000..31d96d9c0f
--- /dev/null
+++ b/scene/resources/visual_shader_nodes.compat.inc
@@ -0,0 +1,63 @@
+/**************************************************************************/
+/* visual_shader_nodes.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
+
+// VisualShaderNodeCubemap
+
+void VisualShaderNodeCubemap::_set_cube_map_bind_compat_95126(Ref<Cubemap> p_cube_map) {
+ set_cube_map(p_cube_map);
+}
+
+Ref<Cubemap> VisualShaderNodeCubemap::_get_cube_map_bind_compat_95126() const {
+ return cube_map;
+}
+
+void VisualShaderNodeCubemap::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("set_cube_map", "value"), &VisualShaderNodeCubemap::_set_cube_map_bind_compat_95126);
+ ClassDB::bind_compatibility_method(D_METHOD("get_cube_map"), &VisualShaderNodeCubemap::_get_cube_map_bind_compat_95126);
+}
+
+// VisualShaderNodeTexture2DArray
+
+void VisualShaderNodeTexture2DArray::_set_texture_array_bind_compat_95126(Ref<Texture2DArray> p_texture_array) {
+ set_texture_array(p_texture_array);
+}
+
+Ref<Texture2DArray> VisualShaderNodeTexture2DArray::_get_texture_array_bind_compat_95126() const {
+ return texture_array;
+}
+
+void VisualShaderNodeTexture2DArray::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("set_texture_array", "value"), &VisualShaderNodeTexture2DArray::_set_texture_array_bind_compat_95126);
+ ClassDB::bind_compatibility_method(D_METHOD("get_texture_array"), &VisualShaderNodeTexture2DArray::_get_texture_array_bind_compat_95126);
+}
+
+#endif // DISABLE_DEPRECATED
diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp
index 5e148c9276..26666538af 100644
--- a/scene/resources/visual_shader_nodes.cpp
+++ b/scene/resources/visual_shader_nodes.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "visual_shader_nodes.h"
+#include "visual_shader_nodes.compat.inc"
#include "scene/resources/image_texture.h"
@@ -1353,12 +1354,12 @@ String VisualShaderNodeTexture2DArray::generate_global(Shader::Mode p_mode, Visu
return String();
}
-void VisualShaderNodeTexture2DArray::set_texture_array(Ref<Texture2DArray> p_texture_array) {
+void VisualShaderNodeTexture2DArray::set_texture_array(Ref<TextureLayered> p_texture_array) {
texture_array = p_texture_array;
emit_changed();
}
-Ref<Texture2DArray> VisualShaderNodeTexture2DArray::get_texture_array() const {
+Ref<TextureLayered> VisualShaderNodeTexture2DArray::get_texture_array() const {
return texture_array;
}
@@ -1375,7 +1376,7 @@ void VisualShaderNodeTexture2DArray::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_texture_array", "value"), &VisualShaderNodeTexture2DArray::set_texture_array);
ClassDB::bind_method(D_METHOD("get_texture_array"), &VisualShaderNodeTexture2DArray::get_texture_array);
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_array", PROPERTY_HINT_RESOURCE_TYPE, "Texture2DArray"), "set_texture_array", "get_texture_array");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_array", PROPERTY_HINT_RESOURCE_TYPE, "Texture2DArray,CompressedTexture2DArray,PlaceholderTexture2DArray,Texture2DArrayRD"), "set_texture_array", "get_texture_array");
}
VisualShaderNodeTexture2DArray::VisualShaderNodeTexture2DArray() {
@@ -1568,12 +1569,12 @@ VisualShaderNodeCubemap::Source VisualShaderNodeCubemap::get_source() const {
return source;
}
-void VisualShaderNodeCubemap::set_cube_map(Ref<Cubemap> p_cube_map) {
+void VisualShaderNodeCubemap::set_cube_map(Ref<TextureLayered> p_cube_map) {
cube_map = p_cube_map;
emit_changed();
}
-Ref<Cubemap> VisualShaderNodeCubemap::get_cube_map() const {
+Ref<TextureLayered> VisualShaderNodeCubemap::get_cube_map() const {
return cube_map;
}
@@ -1618,7 +1619,7 @@ void VisualShaderNodeCubemap::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_texture_type"), &VisualShaderNodeCubemap::get_texture_type);
ADD_PROPERTY(PropertyInfo(Variant::INT, "source", PROPERTY_HINT_ENUM, "Texture,SamplerPort"), "set_source", "get_source");
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "cube_map", PROPERTY_HINT_RESOURCE_TYPE, "Cubemap"), "set_cube_map", "get_cube_map");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "cube_map", PROPERTY_HINT_RESOURCE_TYPE, "Cubemap,CompressedCubemap,PlaceholderCubemap,TextureCubemapRD"), "set_cube_map", "get_cube_map");
ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_type", PROPERTY_HINT_ENUM, "Data,Color,Normal Map"), "set_texture_type", "get_texture_type");
BIND_ENUM_CONSTANT(SOURCE_TEXTURE);
diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h
index 279599ef9c..ff02e55fb2 100644
--- a/scene/resources/visual_shader_nodes.h
+++ b/scene/resources/visual_shader_nodes.h
@@ -31,6 +31,7 @@
#ifndef VISUAL_SHADER_NODES_H
#define VISUAL_SHADER_NODES_H
+#include "scene/resources/compressed_texture.h"
#include "scene/resources/curve_texture.h"
#include "scene/resources/visual_shader.h"
@@ -562,9 +563,15 @@ VARIANT_ENUM_CAST(VisualShaderNodeSample3D::Source)
class VisualShaderNodeTexture2DArray : public VisualShaderNodeSample3D {
GDCLASS(VisualShaderNodeTexture2DArray, VisualShaderNodeSample3D);
- Ref<Texture2DArray> texture_array;
+ Ref<TextureLayered> texture_array;
protected:
+#ifndef DISABLE_DEPRECATED
+ void _set_texture_array_bind_compat_95126(Ref<Texture2DArray> p_texture_array);
+ Ref<Texture2DArray> _get_texture_array_bind_compat_95126() const;
+ static void _bind_compatibility_methods();
+#endif // DISABLE_DEPRECATED
+
static void _bind_methods();
public:
@@ -575,8 +582,8 @@ public:
virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const override;
virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override;
- void set_texture_array(Ref<Texture2DArray> p_texture_array);
- Ref<Texture2DArray> get_texture_array() const;
+ void set_texture_array(Ref<TextureLayered> p_texture_array);
+ Ref<TextureLayered> get_texture_array() const;
virtual Vector<StringName> get_editable_properties() const override;
@@ -608,7 +615,7 @@ public:
class VisualShaderNodeCubemap : public VisualShaderNode {
GDCLASS(VisualShaderNodeCubemap, VisualShaderNode);
- Ref<Cubemap> cube_map;
+ Ref<TextureLayered> cube_map;
public:
enum Source {
@@ -629,6 +636,12 @@ private:
TextureType texture_type = TYPE_DATA;
protected:
+#ifndef DISABLE_DEPRECATED
+ void _set_cube_map_bind_compat_95126(Ref<Cubemap> p_cube_map);
+ Ref<Cubemap> _get_cube_map_bind_compat_95126() const;
+ static void _bind_compatibility_methods();
+#endif // DISABLE_DEPRECATED
+
static void _bind_methods();
public:
@@ -650,8 +663,8 @@ public:
void set_source(Source p_source);
Source get_source() const;
- void set_cube_map(Ref<Cubemap> p_cube_map);
- Ref<Cubemap> get_cube_map() const;
+ void set_cube_map(Ref<TextureLayered> p_cube_map);
+ Ref<TextureLayered> get_cube_map() const;
void set_texture_type(TextureType p_texture_type);
TextureType get_texture_type() const;
diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp
index 40f3ddf048..f8a0336b37 100644
--- a/scene/scene_string_names.cpp
+++ b/scene/scene_string_names.cpp
@@ -132,6 +132,7 @@ SceneStringNames::SceneStringNames() {
pressed = StaticCString::create("pressed");
id_pressed = StaticCString::create("id_pressed");
+ toggled = StaticCString::create("toggled");
panel = StaticCString::create("panel");
diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h
index 7893d213e4..381a161ad5 100644
--- a/scene/scene_string_names.h
+++ b/scene/scene_string_names.h
@@ -145,6 +145,7 @@ public:
StringName pressed;
StringName id_pressed;
+ StringName toggled;
StringName panel;
diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp
index b2a3843b02..8a9e784c47 100644
--- a/scene/theme/default_theme.cpp
+++ b/scene/theme/default_theme.cpp
@@ -504,6 +504,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_icon("can_fold_code_region", "CodeEdit", icons["region_unfolded"]);
theme->set_icon("folded_code_region", "CodeEdit", icons["region_folded"]);
theme->set_icon("folded_eol_icon", "CodeEdit", icons["text_edit_ellipsis"]);
+ theme->set_icon("completion_color_bg", "CodeEdit", icons["mini_checkerboard"]);
theme->set_font(SceneStringName(font), "CodeEdit", Ref<Font>());
theme->set_font_size(SceneStringName(font_size), "CodeEdit", -1);
@@ -613,7 +614,41 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
// SpinBox
- theme->set_icon("updown", "SpinBox", icons["updown"]);
+ theme->set_icon("updown", "SpinBox", empty_icon);
+ theme->set_icon("up", "SpinBox", icons["value_up"]);
+ theme->set_icon("up_hover", "SpinBox", icons["value_up"]);
+ theme->set_icon("up_pressed", "SpinBox", icons["value_up"]);
+ theme->set_icon("up_disabled", "SpinBox", icons["value_up"]);
+ theme->set_icon("down", "SpinBox", icons["value_down"]);
+ theme->set_icon("down_hover", "SpinBox", icons["value_down"]);
+ theme->set_icon("down_pressed", "SpinBox", icons["value_down"]);
+ theme->set_icon("down_disabled", "SpinBox", icons["value_down"]);
+
+ theme->set_stylebox("up_background", "SpinBox", make_empty_stylebox());
+ theme->set_stylebox("up_background_hovered", "SpinBox", button_hover);
+ theme->set_stylebox("up_background_pressed", "SpinBox", button_pressed);
+ theme->set_stylebox("up_background_disabled", "SpinBox", make_empty_stylebox());
+ theme->set_stylebox("down_background", "SpinBox", make_empty_stylebox());
+ theme->set_stylebox("down_background_hovered", "SpinBox", button_hover);
+ theme->set_stylebox("down_background_pressed", "SpinBox", button_pressed);
+ theme->set_stylebox("down_background_disabled", "SpinBox", make_empty_stylebox());
+
+ theme->set_color("up_icon_modulate", "SpinBox", control_font_color);
+ theme->set_color("up_hover_icon_modulate", "SpinBox", control_font_hover_color);
+ theme->set_color("up_pressed_icon_modulate", "SpinBox", control_font_hover_color);
+ theme->set_color("up_disabled_icon_modulate", "SpinBox", control_font_disabled_color);
+ theme->set_color("down_icon_modulate", "SpinBox", control_font_color);
+ theme->set_color("down_hover_icon_modulate", "SpinBox", control_font_hover_color);
+ theme->set_color("down_pressed_icon_modulate", "SpinBox", control_font_hover_color);
+ theme->set_color("down_disabled_icon_modulate", "SpinBox", control_font_disabled_color);
+
+ theme->set_stylebox("field_and_buttons_separator", "SpinBox", make_empty_stylebox());
+ theme->set_stylebox("up_down_buttons_separator", "SpinBox", make_empty_stylebox());
+
+ theme->set_constant("buttons_vertical_separation", "SpinBox", 0);
+ theme->set_constant("field_and_buttons_separation", "SpinBox", 2);
+ theme->set_constant("buttons_width", "SpinBox", 16);
+ theme->set_constant("set_min_buttons_width_from_icons", "SpinBox", 1);
// ScrollContainer
diff --git a/scene/theme/icons/default_theme_icons_builders.py b/scene/theme/icons/default_theme_icons_builders.py
index 49c0a93191..3a673af92e 100644
--- a/scene/theme/icons/default_theme_icons_builders.py
+++ b/scene/theme/icons/default_theme_icons_builders.py
@@ -3,6 +3,8 @@
import os
from io import StringIO
+from methods import to_raw_cstring
+
# See also `editor/icons/editor_icons_builders.py`.
def make_default_theme_icons_action(target, source, env):
@@ -10,21 +12,9 @@ def make_default_theme_icons_action(target, source, env):
svg_icons = [str(x) for x in source]
with StringIO() as icons_string, StringIO() as s:
- for f in svg_icons:
- fname = str(f)
-
- icons_string.write('\t"')
-
- with open(fname, "rb") as svgf:
- b = svgf.read(1)
- while len(b) == 1:
- icons_string.write("\\" + str(hex(ord(b)))[1:])
- b = svgf.read(1)
-
- icons_string.write('"')
- if fname != svg_icons[-1]:
- icons_string.write(",")
- icons_string.write("\n")
+ for svg in svg_icons:
+ with open(svg, "r") as svgf:
+ icons_string.write("\t%s,\n" % to_raw_cstring(svgf.read()))
s.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n\n")
s.write('#include "modules/modules_enabled.gen.h"\n\n')
diff --git a/scene/theme/icons/value_down.svg b/scene/theme/icons/value_down.svg
new file mode 100644
index 0000000000..57837d03fd
--- /dev/null
+++ b/scene/theme/icons/value_down.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="8"><path fill="none" stroke="#fff" stroke-width="2" d="m12 2-4 3.5L4 2"/></svg> \ No newline at end of file
diff --git a/scene/theme/icons/value_up.svg b/scene/theme/icons/value_up.svg
new file mode 100644
index 0000000000..53fb102fe2
--- /dev/null
+++ b/scene/theme/icons/value_up.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="8"><path fill="none" stroke="#fff" stroke-width="2" d="m4 6 4-3.5L12 6"/></svg> \ No newline at end of file
diff --git a/servers/audio/effects/audio_effect_record.cpp b/servers/audio/effects/audio_effect_record.cpp
index e30a8fa99e..54af4738b1 100644
--- a/servers/audio/effects/audio_effect_record.cpp
+++ b/servers/audio/effects/audio_effect_record.cpp
@@ -283,7 +283,7 @@ void AudioEffectRecord::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_format"), &AudioEffectRecord::get_format);
ClassDB::bind_method(D_METHOD("get_recording"), &AudioEffectRecord::get_recording);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA ADPCM,Quite OK Audio"), "set_format", "get_format");
}
AudioEffectRecord::AudioEffectRecord() {
diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp
index f0f894d03b..332f8984a2 100644
--- a/servers/audio_server.cpp
+++ b/servers/audio_server.cpp
@@ -486,10 +486,16 @@ void AudioServer::_mix_step() {
}
// Copy the bus details we mixed with to the previous bus details to maintain volume ramps.
- std::copy(std::begin(bus_details.bus_active), std::end(bus_details.bus_active), std::begin(playback->prev_bus_details->bus_active));
- std::copy(std::begin(bus_details.bus), std::end(bus_details.bus), std::begin(playback->prev_bus_details->bus));
- for (int bus_idx = 0; bus_idx < MAX_BUSES_PER_PLAYBACK; bus_idx++) {
- std::copy(std::begin(bus_details.volume[bus_idx]), std::end(bus_details.volume[bus_idx]), std::begin(playback->prev_bus_details->volume[bus_idx]));
+ for (int i = 0; i < MAX_BUSES_PER_PLAYBACK; i++) {
+ playback->prev_bus_details->bus_active[i] = bus_details.bus_active[i];
+ }
+ for (int i = 0; i < MAX_BUSES_PER_PLAYBACK; i++) {
+ playback->prev_bus_details->bus[i] = bus_details.bus[i];
+ }
+ for (int i = 0; i < MAX_BUSES_PER_PLAYBACK; i++) {
+ for (int j = 0; j < MAX_CHANNELS_PER_BUS; j++) {
+ playback->prev_bus_details->volume[i][j] = bus_details.volume[i][j];
+ }
}
switch (playback->state.load()) {
@@ -497,7 +503,7 @@ void AudioServer::_mix_step() {
case AudioStreamPlaybackListNode::FADE_OUT_TO_DELETION:
playback_list.erase(playback, [](AudioStreamPlaybackListNode *p) {
delete p->prev_bus_details;
- delete p->bus_details;
+ delete p->bus_details.load();
p->stream_playback.unref();
delete p;
});
@@ -1199,7 +1205,7 @@ void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, con
}
idx++;
}
- playback_node->bus_details = new_bus_details;
+ playback_node->bus_details.store(new_bus_details);
playback_node->prev_bus_details = new AudioStreamPlaybackBusDetails();
playback_node->pitch_scale.set(p_pitch_scale);
diff --git a/servers/navigation/navigation_globals.h b/servers/navigation/navigation_globals.h
new file mode 100644
index 0000000000..aa54f95519
--- /dev/null
+++ b/servers/navigation/navigation_globals.h
@@ -0,0 +1,66 @@
+/**************************************************************************/
+/* navigation_globals.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 NAVIGATION_GLOBALS_H
+#define NAVIGATION_GLOBALS_H
+
+namespace NavigationDefaults3D {
+
+// Rasterization.
+
+// To find the polygons edges the vertices are displaced in a grid where
+// each cell has the following cell_size and cell_height.
+constexpr float navmesh_cell_size{ 0.25f }; // Must match ProjectSettings default 3D cell_size and NavigationMesh cell_size.
+constexpr float navmesh_cell_height{ 0.25f }; // Must match ProjectSettings default 3D cell_height and NavigationMesh cell_height.
+constexpr auto navmesh_cell_size_hint{ "0.001,100,0.001,or_greater" };
+
+// Map.
+
+constexpr float edge_connection_margin{ 0.25f };
+constexpr float link_connection_radius{ 1.0f };
+
+} //namespace NavigationDefaults3D
+
+namespace NavigationDefaults2D {
+
+// Rasterization.
+
+// Same as in 3D but larger since 1px is treated as 1m.
+constexpr float navmesh_cell_size{ 1.0f }; // Must match ProjectSettings default 2D cell_size.
+constexpr auto navmesh_cell_size_hint{ "0.001,100,0.001,or_greater" };
+
+// Map.
+
+constexpr float edge_connection_margin{ 1.0f };
+constexpr float link_connection_radius{ 4.0f };
+
+} //namespace NavigationDefaults2D
+
+#endif // NAVIGATION_GLOBALS_H
diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp
index 398ff1e1f3..f4ffcf5a3e 100644
--- a/servers/navigation_server_3d.cpp
+++ b/servers/navigation_server_3d.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "scene/main/node.h"
+#include "servers/navigation/navigation_globals.h"
NavigationServer3D *NavigationServer3D::singleton = nullptr;
@@ -227,18 +228,18 @@ NavigationServer3D::NavigationServer3D() {
ERR_FAIL_COND(singleton != nullptr);
singleton = this;
- GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "navigation/2d/default_cell_size", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), 1.0);
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "navigation/2d/default_cell_size", PROPERTY_HINT_RANGE, NavigationDefaults2D::navmesh_cell_size_hint), NavigationDefaults2D::navmesh_cell_size);
GLOBAL_DEF("navigation/2d/use_edge_connections", true);
- GLOBAL_DEF_BASIC("navigation/2d/default_edge_connection_margin", 1.0);
- GLOBAL_DEF_BASIC("navigation/2d/default_link_connection_radius", 4.0);
+ GLOBAL_DEF_BASIC("navigation/2d/default_edge_connection_margin", NavigationDefaults2D::edge_connection_margin);
+ GLOBAL_DEF_BASIC("navigation/2d/default_link_connection_radius", NavigationDefaults2D::link_connection_radius);
- GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "navigation/3d/default_cell_size", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), 0.25);
- GLOBAL_DEF_BASIC("navigation/3d/default_cell_height", 0.25);
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "navigation/3d/default_cell_size", PROPERTY_HINT_RANGE, NavigationDefaults3D::navmesh_cell_size_hint), NavigationDefaults3D::navmesh_cell_size);
+ GLOBAL_DEF_BASIC("navigation/3d/default_cell_height", NavigationDefaults3D::navmesh_cell_height);
GLOBAL_DEF("navigation/3d/default_up", Vector3(0, 1, 0));
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "navigation/3d/merge_rasterizer_cell_scale", PROPERTY_HINT_RANGE, "0.001,1,0.001,or_greater"), 1.0);
GLOBAL_DEF("navigation/3d/use_edge_connections", true);
- GLOBAL_DEF_BASIC("navigation/3d/default_edge_connection_margin", 0.25);
- GLOBAL_DEF_BASIC("navigation/3d/default_link_connection_radius", 1.0);
+ GLOBAL_DEF_BASIC("navigation/3d/default_edge_connection_margin", NavigationDefaults3D::edge_connection_margin);
+ GLOBAL_DEF_BASIC("navigation/3d/default_link_connection_radius", NavigationDefaults3D::link_connection_radius);
GLOBAL_DEF("navigation/avoidance/thread_model/avoidance_use_multiple_threads", true);
GLOBAL_DEF("navigation/avoidance/thread_model/avoidance_use_high_priority_threads", true);
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index b97e38da4d..36bd22b723 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -2231,7 +2231,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
}
RID alpha_framebuffer = rb_data.is_valid() ? rb_data->get_color_pass_fb(transparent_color_pass_flags) : color_only_framebuffer;
- RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), false, PASS_MODE_COLOR, transparent_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags);
+ RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR, transparent_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags);
_render_list_with_draw_list(&render_list_params, alpha_framebuffer, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE);
}
diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
index 42e1f7b6dc..6846c3f693 100644
--- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
@@ -636,6 +636,7 @@ void SceneShaderForwardClustered::init(const String p_defines) {
actions.renames["CUSTOM2"] = "custom2_attrib";
actions.renames["CUSTOM3"] = "custom3_attrib";
actions.renames["OUTPUT_IS_SRGB"] = "SHADER_IS_SRGB";
+ actions.renames["CLIP_SPACE_FAR"] = "SHADER_SPACE_FAR";
actions.renames["LIGHT_VERTEX"] = "light_vertex";
actions.renames["NODE_POSITION_WORLD"] = "read_model_matrix[3].xyz";
diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
index cf661bb8f4..08982096c5 100644
--- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
@@ -540,6 +540,7 @@ void SceneShaderForwardMobile::init(const String p_defines) {
actions.renames["CUSTOM2"] = "custom2_attrib";
actions.renames["CUSTOM3"] = "custom3_attrib";
actions.renames["OUTPUT_IS_SRGB"] = "SHADER_IS_SRGB";
+ actions.renames["CLIP_SPACE_FAR"] = "SHADER_SPACE_FAR";
actions.renames["LIGHT_VERTEX"] = "light_vertex";
actions.renames["NODE_POSITION_WORLD"] = "read_model_matrix[3].xyz";
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 1420e7939a..35d29fed6a 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
@@ -7,6 +7,7 @@
#include "scene_forward_clustered_inc.glsl"
#define SHADER_IS_SRGB false
+#define SHADER_SPACE_FAR 0.0
/* INPUT ATTRIBS */
@@ -638,6 +639,7 @@ void main() {
#VERSION_DEFINES
#define SHADER_IS_SRGB false
+#define SHADER_SPACE_FAR 0.0
/* Specialization Constants (Toggles) */
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 90947aca80..c266161834 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
@@ -8,6 +8,7 @@
#include "scene_forward_mobile_inc.glsl"
#define SHADER_IS_SRGB false
+#define SHADER_SPACE_FAR 0.0
/* INPUT ATTRIBS */
@@ -498,6 +499,7 @@ void main() {
#VERSION_DEFINES
#define SHADER_IS_SRGB false
+#define SHADER_SPACE_FAR 0.0
/* Specialization Constants */
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.h b/servers/rendering/renderer_rd/storage_rd/light_storage.h
index 94ab219dc2..1db58d72f9 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h
@@ -49,7 +49,7 @@ namespace RendererRD {
class LightStorage : public RendererLightStorage {
public:
- enum ShadowAtlastQuadrant {
+ enum ShadowAtlastQuadrant : uint32_t {
QUADRANT_SHIFT = 27,
OMNI_LIGHT_FLAG = 1 << 26,
SHADOW_INDEX_MASK = OMNI_LIGHT_FLAG - 1,
diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
index 6e5e8f63e0..be29716f45 100644
--- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
@@ -1470,8 +1470,24 @@ void TextureStorage::texture_debug_usage(List<RS::TextureInfo> *r_info) {
tinfo.format = t->format;
tinfo.width = t->width;
tinfo.height = t->height;
- tinfo.depth = t->depth;
- tinfo.bytes = Image::get_image_data_size(t->width, t->height, t->format, t->mipmaps);
+ tinfo.bytes = Image::get_image_data_size(t->width, t->height, t->format, t->mipmaps > 1);
+
+ switch (t->type) {
+ case TextureType::TYPE_3D:
+ tinfo.depth = t->depth;
+ tinfo.bytes *= t->depth;
+ break;
+
+ case TextureType::TYPE_LAYERED:
+ tinfo.depth = t->layers;
+ tinfo.bytes *= t->layers;
+ break;
+
+ default:
+ tinfo.depth = 0;
+ break;
+ }
+
r_info->push_back(tinfo);
}
}
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index 9e3ab5da49..f0f267c246 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -82,11 +82,12 @@ static String _get_device_type_name(const RenderingContextDriver::Device &p_devi
}
static uint32_t _get_device_type_score(const RenderingContextDriver::Device &p_device) {
+ static const bool prefer_integrated = OS::get_singleton()->get_user_prefers_integrated_gpu();
switch (p_device.type) {
case RenderingContextDriver::DEVICE_TYPE_INTEGRATED_GPU:
- return 4;
+ return prefer_integrated ? 5 : 4;
case RenderingContextDriver::DEVICE_TYPE_DISCRETE_GPU:
- return 5;
+ return prefer_integrated ? 4 : 5;
case RenderingContextDriver::DEVICE_TYPE_VIRTUAL_GPU:
return 3;
case RenderingContextDriver::DEVICE_TYPE_CPU:
@@ -3083,7 +3084,7 @@ RID RenderingDevice::uniform_set_create(const Vector<Uniform> &p_uniforms, RID p
ERR_FAIL_NULL_V_MSG(buffer, RID(), "Uniform buffer supplied (binding: " + itos(uniform.binding) + ") is invalid.");
ERR_FAIL_COND_V_MSG(buffer->size < (uint32_t)set_uniform.length, RID(),
- "Uniform buffer supplied (binding: " + itos(uniform.binding) + ") size (" + itos(buffer->size) + " is smaller than size of shader uniform: (" + itos(set_uniform.length) + ").");
+ "Uniform buffer supplied (binding: " + itos(uniform.binding) + ") size (" + itos(buffer->size) + ") is smaller than size of shader uniform: (" + itos(set_uniform.length) + ").");
if (buffer->draw_tracker != nullptr) {
draw_trackers.push_back(buffer->draw_tracker);
@@ -3112,7 +3113,7 @@ RID RenderingDevice::uniform_set_create(const Vector<Uniform> &p_uniforms, RID p
// If 0, then it's sized on link time.
ERR_FAIL_COND_V_MSG(set_uniform.length > 0 && buffer->size != (uint32_t)set_uniform.length, RID(),
- "Storage buffer supplied (binding: " + itos(uniform.binding) + ") size (" + itos(buffer->size) + " does not match size of shader uniform: (" + itos(set_uniform.length) + ").");
+ "Storage buffer supplied (binding: " + itos(uniform.binding) + ") size (" + itos(buffer->size) + ") does not match size of shader uniform: (" + itos(set_uniform.length) + ").");
if (set_uniform.writable && _buffer_make_mutable(buffer, buffer_id)) {
// The buffer must be mutable if it's used for writing.
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index d8f9e2c31a..1405f585b2 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -377,7 +377,9 @@ public:
// used for the render pipelines.
struct AttachmentFormat {
- enum { UNUSED_ATTACHMENT = 0xFFFFFFFF };
+ enum : uint32_t {
+ UNUSED_ATTACHMENT = 0xFFFFFFFF
+ };
DataFormat format;
TextureSamples samples;
uint32_t usage_flags;
diff --git a/servers/rendering/rendering_device_binds.cpp b/servers/rendering/rendering_device_binds.cpp
index 986f01a52c..d9ca286b15 100644
--- a/servers/rendering/rendering_device_binds.cpp
+++ b/servers/rendering/rendering_device_binds.cpp
@@ -112,7 +112,7 @@ Error RDShaderFile::parse_versions_from_text(const String &p_text, const String
}
Vector<String> slices = l.get_slice(";", 0).split("=");
String version = slices[0].strip_edges();
- if (!version.is_valid_identifier()) {
+ if (!version.is_valid_ascii_identifier()) {
base_error = "Version names must be valid identifiers, found '" + version + "' instead.";
break;
}
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index 8457a09055..4eaf7fcb55 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -5196,7 +5196,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_array_constructor(BlockNode *p_bloc
return an;
}
-ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, const FunctionInfo &p_function_info) {
+ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, const FunctionInfo &p_function_info, const ExpressionInfo *p_previous_expression_info) {
Vector<Expression> expression;
//Vector<TokenType> operators;
@@ -6551,6 +6551,10 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
pos = _get_tkpos();
tk = _get_token();
+ if (p_previous_expression_info != nullptr && tk.type == p_previous_expression_info->tt_break && !p_previous_expression_info->is_last_expr) {
+ break;
+ }
+
if (is_token_operator(tk.type)) {
Expression o;
o.is_op = true;
@@ -6657,6 +6661,31 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
expression.push_back(o);
+ if (o.op == OP_SELECT_IF) {
+ ExpressionInfo info;
+ info.expression = &expression;
+ info.tt_break = TK_COLON;
+
+ expr = _parse_and_reduce_expression(p_block, p_function_info, &info);
+ if (!expr) {
+ return nullptr;
+ }
+
+ expression.push_back({ true, { OP_SELECT_ELSE } });
+
+ if (p_previous_expression_info != nullptr) {
+ info.is_last_expr = p_previous_expression_info->is_last_expr;
+ } else {
+ info.is_last_expr = true;
+ }
+
+ expr = _parse_and_reduce_expression(p_block, p_function_info, &info);
+ if (!expr) {
+ return nullptr;
+ }
+
+ break;
+ }
} else {
_set_tkpos(pos); //something else, so rollback and end
break;
@@ -6969,6 +6998,10 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
}
}
+ if (p_previous_expression_info != nullptr) {
+ p_previous_expression_info->expression->push_back(expression[0]);
+ }
+
return expression[0].node;
}
@@ -7081,8 +7114,8 @@ ShaderLanguage::Node *ShaderLanguage::_reduce_expression(BlockNode *p_block, Sha
return p_node;
}
-ShaderLanguage::Node *ShaderLanguage::_parse_and_reduce_expression(BlockNode *p_block, const FunctionInfo &p_function_info) {
- ShaderLanguage::Node *expr = _parse_expression(p_block, p_function_info);
+ShaderLanguage::Node *ShaderLanguage::_parse_and_reduce_expression(BlockNode *p_block, const FunctionInfo &p_function_info, const ExpressionInfo *p_previous_expression_info) {
+ ShaderLanguage::Node *expr = _parse_expression(p_block, p_function_info, p_previous_expression_info);
if (!expr) { //errored
return nullptr;
}
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index 1b5df7e90f..63dca99654 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -747,6 +747,12 @@ public:
};
};
+ struct ExpressionInfo {
+ Vector<Expression> *expression = nullptr;
+ TokenType tt_break = TK_EMPTY;
+ bool is_last_expr = false;
+ };
+
struct VarInfo {
StringName name;
DataType type;
@@ -1143,13 +1149,13 @@ private:
bool _check_restricted_func(const StringName &p_name, const StringName &p_current_function) const;
bool _validate_restricted_func(const StringName &p_call_name, const CallInfo *p_func_info, bool p_is_builtin_hint = false);
- Node *_parse_expression(BlockNode *p_block, const FunctionInfo &p_function_info);
+ Node *_parse_expression(BlockNode *p_block, const FunctionInfo &p_function_info, const ExpressionInfo *p_previous_expression_info = nullptr);
Error _parse_array_size(BlockNode *p_block, const FunctionInfo &p_function_info, bool p_forbid_unknown_size, Node **r_size_expression, int *r_array_size, bool *r_unknown_size);
Node *_parse_array_constructor(BlockNode *p_block, const FunctionInfo &p_function_info);
Node *_parse_array_constructor(BlockNode *p_block, const FunctionInfo &p_function_info, DataType p_type, const StringName &p_struct_name, int p_array_size);
ShaderLanguage::Node *_reduce_expression(BlockNode *p_block, ShaderLanguage::Node *p_node);
- Node *_parse_and_reduce_expression(BlockNode *p_block, const FunctionInfo &p_function_info);
+ Node *_parse_and_reduce_expression(BlockNode *p_block, const FunctionInfo &p_function_info, const ExpressionInfo *p_previous_expression_info = nullptr);
Error _parse_block(BlockNode *p_block, const FunctionInfo &p_function_info, bool p_just_one = false, bool p_can_break = false, bool p_can_continue = false);
String _get_shader_type_list(const HashSet<String> &p_shader_types) const;
String _get_qualifier_str(ArgumentQualifier p_qualifier) const;
diff --git a/servers/rendering/shader_preprocessor.cpp b/servers/rendering/shader_preprocessor.cpp
index dbd1374941..27e39551ba 100644
--- a/servers/rendering/shader_preprocessor.cpp
+++ b/servers/rendering/shader_preprocessor.cpp
@@ -173,7 +173,7 @@ String ShaderPreprocessor::Tokenizer::get_identifier(bool *r_is_cursor, bool p_s
}
String id = vector_to_string(text);
- if (!id.is_valid_identifier()) {
+ if (!id.is_valid_ascii_identifier()) {
return "";
}
diff --git a/servers/rendering/shader_types.cpp b/servers/rendering/shader_types.cpp
index de396cd18b..f498c0bf93 100644
--- a/servers/rendering/shader_types.cpp
+++ b/servers/rendering/shader_types.cpp
@@ -97,6 +97,7 @@ ShaderTypes::ShaderTypes() {
shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["MODELVIEW_NORMAL_MATRIX"] = ShaderLanguage::TYPE_MAT3;
shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["VIEWPORT_SIZE"] = constt(ShaderLanguage::TYPE_VEC2);
shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["OUTPUT_IS_SRGB"] = constt(ShaderLanguage::TYPE_BOOL);
+ shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["CLIP_SPACE_FAR"] = constt(ShaderLanguage::TYPE_FLOAT);
shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["MAIN_CAM_INV_VIEW_MATRIX"] = constt(ShaderLanguage::TYPE_MAT4);
shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["NODE_POSITION_WORLD"] = constt(ShaderLanguage::TYPE_VEC3);
@@ -159,6 +160,7 @@ ShaderTypes::ShaderTypes() {
shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["EYE_OFFSET"] = constt(ShaderLanguage::TYPE_VEC3);
shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["OUTPUT_IS_SRGB"] = constt(ShaderLanguage::TYPE_BOOL);
+ shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["CLIP_SPACE_FAR"] = constt(ShaderLanguage::TYPE_FLOAT);
shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["MODEL_MATRIX"] = constt(ShaderLanguage::TYPE_MAT4);
shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["MODEL_NORMAL_MATRIX"] = constt(ShaderLanguage::TYPE_MAT3);
@@ -202,6 +204,7 @@ ShaderTypes::ShaderTypes() {
shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["DIFFUSE_LIGHT"] = ShaderLanguage::TYPE_VEC3;
shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["SPECULAR_LIGHT"] = ShaderLanguage::TYPE_VEC3;
shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["OUTPUT_IS_SRGB"] = constt(ShaderLanguage::TYPE_BOOL);
+ shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["CLIP_SPACE_FAR"] = constt(ShaderLanguage::TYPE_FLOAT);
shader_modes[RS::SHADER_SPATIAL].functions["light"].built_ins["ALPHA"] = ShaderLanguage::TYPE_FLOAT;
shader_modes[RS::SHADER_SPATIAL].functions["light"].can_discard = true;
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 92f0f0dbc0..1848d5602e 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -3263,6 +3263,7 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("canvas_item_set_z_index", "item", "z_index"), &RenderingServer::canvas_item_set_z_index);
ClassDB::bind_method(D_METHOD("canvas_item_set_z_as_relative_to_parent", "item", "enabled"), &RenderingServer::canvas_item_set_z_as_relative_to_parent);
ClassDB::bind_method(D_METHOD("canvas_item_set_copy_to_backbuffer", "item", "enabled", "rect"), &RenderingServer::canvas_item_set_copy_to_backbuffer);
+ ClassDB::bind_method(D_METHOD("canvas_item_attach_skeleton", "item", "skeleton"), &RenderingServer::canvas_item_attach_skeleton);
ClassDB::bind_method(D_METHOD("canvas_item_clear", "item"), &RenderingServer::canvas_item_clear);
ClassDB::bind_method(D_METHOD("canvas_item_set_draw_index", "item", "index"), &RenderingServer::canvas_item_set_draw_index);
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index d8b6651833..62ca6b3b6d 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -176,7 +176,7 @@ public:
uint32_t height;
uint32_t depth;
Image::Format format;
- int bytes;
+ int64_t bytes;
String path;
};
diff --git a/servers/text_server.cpp b/servers/text_server.cpp
index f391c79514..860cc5d75d 100644
--- a/servers/text_server.cpp
+++ b/servers/text_server.cpp
@@ -2165,23 +2165,7 @@ TypedArray<Dictionary> TextServer::_shaped_text_get_ellipsis_glyphs_wrapper(cons
}
bool TextServer::is_valid_identifier(const String &p_string) const {
- const char32_t *str = p_string.ptr();
- int len = p_string.length();
-
- if (len == 0) {
- return false; // Empty string.
- }
-
- if (!is_unicode_identifier_start(str[0])) {
- return false;
- }
-
- for (int i = 1; i < len; i++) {
- if (!is_unicode_identifier_continue(str[i])) {
- return false;
- }
- }
- return true;
+ return p_string.is_valid_unicode_identifier();
}
bool TextServer::is_valid_letter(uint64_t p_unicode) const {
diff --git a/tests/core/io/test_json_native.h b/tests/core/io/test_json_native.h
new file mode 100644
index 0000000000..819078ac57
--- /dev/null
+++ b/tests/core/io/test_json_native.h
@@ -0,0 +1,160 @@
+/**************************************************************************/
+/* test_json_native.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_JSON_NATIVE_H
+#define TEST_JSON_NATIVE_H
+
+#include "core/io/json.h"
+
+namespace TestJSONNative {
+
+bool compare_variants(Variant variant_1, Variant variant_2, int depth = 0) {
+ if (depth > 100) {
+ return false;
+ }
+ if (variant_1.get_type() == Variant::RID && variant_2.get_type() == Variant::RID) {
+ return true;
+ }
+ if (variant_1.get_type() == Variant::CALLABLE || variant_2.get_type() == Variant::CALLABLE) {
+ return true;
+ }
+
+ List<PropertyInfo> variant_1_properties;
+ variant_1.get_property_list(&variant_1_properties);
+ List<PropertyInfo> variant_2_properties;
+ variant_2.get_property_list(&variant_2_properties);
+
+ if (variant_1_properties.size() != variant_2_properties.size()) {
+ return false;
+ }
+
+ for (List<PropertyInfo>::Element *E = variant_1_properties.front(); E; E = E->next()) {
+ String name = E->get().name;
+ Variant variant_1_value = variant_1.get(name);
+ Variant variant_2_value = variant_2.get(name);
+
+ if (!compare_variants(variant_1_value, variant_2_value, depth + 1)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+TEST_CASE("[JSON][Native][SceneTree] Conversion between native and JSON formats") {
+ for (int variant_i = 0; variant_i < Variant::VARIANT_MAX; variant_i++) {
+ Variant::Type type = static_cast<Variant::Type>(variant_i);
+ Variant native_data;
+ Callable::CallError error;
+
+ if (type == Variant::Type::INT || type == Variant::Type::FLOAT) {
+ Variant value = int64_t(INT64_MAX);
+ const Variant *args[] = { &value };
+ Variant::construct(type, native_data, args, 1, error);
+ } else if (type == Variant::Type::OBJECT) {
+ Ref<JSON> json = memnew(JSON);
+ native_data = json;
+ } else if (type == Variant::Type::DICTIONARY) {
+ Dictionary dictionary;
+ dictionary["key"] = "value";
+ native_data = dictionary;
+ } else if (type == Variant::Type::ARRAY) {
+ Array array;
+ array.push_back("element1");
+ array.push_back("element2");
+ native_data = array;
+ } else if (type == Variant::Type::PACKED_BYTE_ARRAY) {
+ PackedByteArray packed_array;
+ packed_array.push_back(1);
+ packed_array.push_back(2);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_INT32_ARRAY) {
+ PackedInt32Array packed_array;
+ packed_array.push_back(INT32_MIN);
+ packed_array.push_back(INT32_MAX);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_INT64_ARRAY) {
+ PackedInt64Array packed_array;
+ packed_array.push_back(INT64_MIN);
+ packed_array.push_back(INT64_MAX);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_FLOAT32_ARRAY) {
+ PackedFloat32Array packed_array;
+ packed_array.push_back(FLT_MIN);
+ packed_array.push_back(FLT_MAX);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_FLOAT64_ARRAY) {
+ PackedFloat64Array packed_array;
+ packed_array.push_back(DBL_MIN);
+ packed_array.push_back(DBL_MAX);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_STRING_ARRAY) {
+ PackedStringArray packed_array;
+ packed_array.push_back("string1");
+ packed_array.push_back("string2");
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_VECTOR2_ARRAY) {
+ PackedVector2Array packed_array;
+ Vector2 vector(1.0, 2.0);
+ packed_array.push_back(vector);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_VECTOR3_ARRAY) {
+ PackedVector3Array packed_array;
+ Vector3 vector(1.0, 2.0, 3.0);
+ packed_array.push_back(vector);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_COLOR_ARRAY) {
+ PackedColorArray packed_array;
+ Color color(1.0, 1.0, 1.0);
+ packed_array.push_back(color);
+ native_data = packed_array;
+ } else if (type == Variant::Type::PACKED_VECTOR4_ARRAY) {
+ PackedVector4Array packed_array;
+ Vector4 vector(1.0, 2.0, 3.0, 4.0);
+ packed_array.push_back(vector);
+ native_data = packed_array;
+ } else {
+ Variant::construct(type, native_data, nullptr, 0, error);
+ }
+ Variant json_converted_from_native = JSON::from_native(native_data, true, true);
+ Variant variant_native_converted = JSON::to_native(json_converted_from_native, true, true);
+ CHECK_MESSAGE(compare_variants(native_data, variant_native_converted),
+ vformat("Conversion from native to JSON type %s and back successful. \nNative: %s \nNative Converted: %s \nError: %s\nConversion from native to JSON type %s successful: %s",
+ Variant::get_type_name(type),
+ native_data,
+ variant_native_converted,
+ itos(error.error),
+ Variant::get_type_name(type),
+ json_converted_from_native));
+ }
+}
+} // namespace TestJSONNative
+
+#endif // TEST_JSON_NATIVE_H
diff --git a/tests/core/object/test_class_db.h b/tests/core/object/test_class_db.h
index 8515ba7644..924e93129d 100644
--- a/tests/core/object/test_class_db.h
+++ b/tests/core/object/test_class_db.h
@@ -603,7 +603,7 @@ void add_exposed_classes(Context &r_context) {
MethodData method;
method.name = method_info.name;
- TEST_FAIL_COND(!String(method.name).is_valid_identifier(),
+ TEST_FAIL_COND(!String(method.name).is_valid_ascii_identifier(),
"Method name is not a valid identifier: '", exposed_class.name, ".", method.name, "'.");
if (method_info.flags & METHOD_FLAG_VIRTUAL) {
@@ -729,7 +729,7 @@ void add_exposed_classes(Context &r_context) {
const MethodInfo &method_info = signal_map.get(K.key);
signal.name = method_info.name;
- TEST_FAIL_COND(!String(signal.name).is_valid_identifier(),
+ TEST_FAIL_COND(!String(signal.name).is_valid_ascii_identifier(),
"Signal name is not a valid identifier: '", exposed_class.name, ".", signal.name, "'.");
int i = 0;
diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h
index 57bc65328a..f1bb62cb70 100644
--- a/tests/core/object/test_object.h
+++ b/tests/core/object/test_object.h
@@ -174,6 +174,31 @@ TEST_CASE("[Object] Metadata") {
CHECK_MESSAGE(
meta_list2.size() == 0,
"The metadata list should contain 0 items after removing all metadata items.");
+
+ Object other;
+ object.set_meta("conflicting_meta", "string");
+ object.set_meta("not_conflicting_meta", 123);
+ other.set_meta("conflicting_meta", Color(0, 1, 0));
+ other.set_meta("other_meta", "other");
+ object.merge_meta_from(&other);
+
+ CHECK_MESSAGE(
+ Color(object.get_meta("conflicting_meta")).is_equal_approx(Color(0, 1, 0)),
+ "String meta should be overwritten with Color after merging.");
+
+ CHECK_MESSAGE(
+ int(object.get_meta("not_conflicting_meta")) == 123,
+ "Not conflicting meta on destination should be kept intact.");
+
+ CHECK_MESSAGE(
+ object.get_meta("other_meta", String()) == "other",
+ "Not conflicting meta name on source should merged.");
+
+ List<StringName> meta_list3;
+ object.get_meta_list(&meta_list3);
+ CHECK_MESSAGE(
+ meta_list3.size() == 3,
+ "The metadata list should contain 3 items after merging meta from two objects.");
}
TEST_CASE("[Object] Construction") {
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index 933eeff524..a9f615af84 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -433,6 +433,19 @@ TEST_CASE("[String] Insertion") {
String s = "Who is Frederic?";
s = s.insert(s.find("?"), " Chopin");
CHECK(s == "Who is Frederic Chopin?");
+
+ s = "foobar";
+ CHECK(s.insert(0, "X") == "Xfoobar");
+ CHECK(s.insert(-100, "X") == "foobar");
+ CHECK(s.insert(6, "X") == "foobarX");
+ CHECK(s.insert(100, "X") == "foobarX");
+ CHECK(s.insert(2, "") == "foobar");
+
+ s = "";
+ CHECK(s.insert(0, "abc") == "abc");
+ CHECK(s.insert(100, "abc") == "abc");
+ CHECK(s.insert(-100, "abc") == "");
+ CHECK(s.insert(0, "") == "");
}
TEST_CASE("[String] Erasing") {
@@ -1811,31 +1824,45 @@ TEST_CASE("[String] SHA1/SHA256/MD5") {
}
TEST_CASE("[String] Join") {
- String s = ", ";
+ String comma = ", ";
+ String empty = "";
Vector<String> parts;
+
+ CHECK(comma.join(parts) == "");
+ CHECK(empty.join(parts) == "");
+
parts.push_back("One");
+ CHECK(comma.join(parts) == "One");
+ CHECK(empty.join(parts) == "One");
+
parts.push_back("B");
parts.push_back("C");
- String t = s.join(parts);
- CHECK(t == "One, B, C");
+ CHECK(comma.join(parts) == "One, B, C");
+ CHECK(empty.join(parts) == "OneBC");
+
+ parts.push_back("");
+ CHECK(comma.join(parts) == "One, B, C, ");
+ CHECK(empty.join(parts) == "OneBC");
}
TEST_CASE("[String] Is_*") {
- static const char *data[12] = { "-30", "100", "10.1", "10,1", "1e2", "1e-2", "1e2e3", "0xAB", "AB", "Test1", "1Test", "Test*1" };
- static bool isnum[12] = { true, true, true, false, false, false, false, false, false, false, false, false };
- static bool isint[12] = { true, true, false, false, false, false, false, false, false, false, false, false };
- static bool ishex[12] = { true, true, false, false, true, false, true, false, true, false, false, false };
- static bool ishex_p[12] = { false, false, false, false, false, false, false, true, false, false, false, false };
- static bool isflt[12] = { true, true, true, false, true, true, false, false, false, false, false, false };
- static bool isid[12] = { false, false, false, false, false, false, false, false, true, true, false, false };
+ static const char *data[13] = { "-30", "100", "10.1", "10,1", "1e2", "1e-2", "1e2e3", "0xAB", "AB", "Test1", "1Test", "Test*1", "文字" };
+ static bool isnum[13] = { true, true, true, false, false, false, false, false, false, false, false, false, false };
+ static bool isint[13] = { true, true, false, false, false, false, false, false, false, false, false, false, false };
+ static bool ishex[13] = { true, true, false, false, true, false, true, false, true, false, false, false, false };
+ static bool ishex_p[13] = { false, false, false, false, false, false, false, true, false, false, false, false, false };
+ static bool isflt[13] = { true, true, true, false, true, true, false, false, false, false, false, false, false };
+ static bool isaid[13] = { false, false, false, false, false, false, false, false, true, true, false, false, false };
+ static bool isuid[13] = { false, false, false, false, false, false, false, false, true, true, false, false, true };
for (int i = 0; i < 12; i++) {
- String s = String(data[i]);
+ String s = String::utf8(data[i]);
CHECK(s.is_numeric() == isnum[i]);
CHECK(s.is_valid_int() == isint[i]);
CHECK(s.is_valid_hex_number(false) == ishex[i]);
CHECK(s.is_valid_hex_number(true) == ishex_p[i]);
CHECK(s.is_valid_float() == isflt[i]);
- CHECK(s.is_valid_identifier() == isid[i]);
+ CHECK(s.is_valid_ascii_identifier() == isaid[i]);
+ CHECK(s.is_valid_unicode_identifier() == isuid[i]);
}
}
@@ -1863,16 +1890,16 @@ TEST_CASE("[String] validate_node_name") {
TEST_CASE("[String] validate_identifier") {
String empty_string;
- CHECK(empty_string.validate_identifier() == "_");
+ CHECK(empty_string.validate_ascii_identifier() == "_");
String numeric_only = "12345";
- CHECK(numeric_only.validate_identifier() == "_12345");
+ CHECK(numeric_only.validate_ascii_identifier() == "_12345");
String name_with_spaces = "Name with spaces";
- CHECK(name_with_spaces.validate_identifier() == "Name_with_spaces");
+ CHECK(name_with_spaces.validate_ascii_identifier() == "Name_with_spaces");
String name_with_invalid_chars = U"Invalid characters:@*#&世界";
- CHECK(name_with_invalid_chars.validate_identifier() == "Invalid_characters_______");
+ CHECK(name_with_invalid_chars.validate_ascii_identifier() == "Invalid_characters_______");
}
TEST_CASE("[String] Variant indexed get") {
diff --git a/tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl b/tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl
index 8ad5a23eb5..db5f54e3d8 100644
--- a/tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl
+++ b/tests/python_build/fixtures/gles3/vertex_fragment_expected_full.glsl
@@ -37,10 +37,34 @@ protected:
static const Feedback* _feedbacks=nullptr;
static const char _vertex_code[]={
-10,112,114,101,99,105,115,105,111,110,32,104,105,103,104,112,32,102,108,111,97,116,59,10,112,114,101,99,105,115,105,111,110,32,104,105,103,104,112,32,105,110,116,59,10,10,108,97,121,111,117,116,40,108,111,99,97,116,105,111,110,32,61,32,48,41,32,105,110,32,104,105,103,104,112,32,118,101,99,51,32,118,101,114,116,101,120,59,10,10,111,117,116,32,104,105,103,104,112,32,118,101,99,52,32,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,32,61,32,118,101,99,52,40,118,101,114,116,101,120,46,120,44,49,44,48,44,49,41,59,10,125,10,10, 0};
+R"<!>(
+precision highp float;
+precision highp int;
+
+layout(location = 0) in highp vec3 vertex;
+
+out highp vec4 position_interp;
+
+void main() {
+ position_interp = vec4(vertex.x,1,0,1);
+}
+
+)<!>"
+ };
static const char _fragment_code[]={
-10,112,114,101,99,105,115,105,111,110,32,104,105,103,104,112,32,102,108,111,97,116,59,10,112,114,101,99,105,115,105,111,110,32,104,105,103,104,112,32,105,110,116,59,10,10,105,110,32,104,105,103,104,112,32,118,101,99,52,32,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,104,105,103,104,112,32,102,108,111,97,116,32,100,101,112,116,104,32,61,32,40,40,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,46,122,32,47,32,112,111,115,105,116,105,111,110,95,105,110,116,101,114,112,46,119,41,32,43,32,49,46,48,41,59,10,9,102,114,97,103,95,99,111,108,111,114,32,61,32,118,101,99,52,40,100,101,112,116,104,41,59,10,125,10, 0};
+R"<!>(
+precision highp float;
+precision highp int;
+
+in highp vec4 position_interp;
+
+void main() {
+ highp float depth = ((position_interp.z / position_interp.w) + 1.0);
+ frag_color = vec4(depth);
+}
+)<!>"
+ };
_setup(_vertex_code,_fragment_code,"VertexFragmentShaderGLES3",0,_uniform_strings,0,_ubo_pairs,0,_feedbacks,0,_texunit_pairs,1,_spec_pairs,1,_variant_defines);
}
diff --git a/tests/python_build/fixtures/glsl/compute_expected_full.glsl b/tests/python_build/fixtures/glsl/compute_expected_full.glsl
index b937d732c8..386d14f1aa 100644
--- a/tests/python_build/fixtures/glsl/compute_expected_full.glsl
+++ b/tests/python_build/fixtures/glsl/compute_expected_full.glsl
@@ -3,6 +3,18 @@
#define COMPUTE_SHADER_GLSL_RAW_H
static const char compute_shader_glsl[] = {
- 35,91,99,111,109,112,117,116,101,93,10,10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,10,35,100,101,102,105,110,101,32,77,95,80,73,32,51,46,49,52,49,53,57,50,54,53,51,53,57,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,118,101,99,51,32,115,116,97,116,105,99,95,108,105,103,104,116,32,61,32,118,101,99,51,40,48,44,32,49,44,32,48,41,59,10,125,10,0
+R"<!>(#[compute]
+
+#version 450
+
+#VERSION_DEFINES
+
+
+#define M_PI 3.14159265359
+
+void main() {
+ vec3 static_light = vec3(0, 1, 0);
+}
+)<!>"
};
#endif
diff --git a/tests/python_build/fixtures/glsl/vertex_fragment_expected_full.glsl b/tests/python_build/fixtures/glsl/vertex_fragment_expected_full.glsl
index 3f53a17fac..b7329b6a79 100644
--- a/tests/python_build/fixtures/glsl/vertex_fragment_expected_full.glsl
+++ b/tests/python_build/fixtures/glsl/vertex_fragment_expected_full.glsl
@@ -3,6 +3,38 @@
#define VERTEX_FRAGMENT_SHADER_GLSL_RAW_H
static const char vertex_fragment_shader_glsl[] = {
- 35,91,118,101,114,115,105,111,110,115,93,10,10,108,105,110,101,115,32,61,32,34,35,100,101,102,105,110,101,32,77,79,68,69,95,76,73,78,69,83,34,59,10,10,35,91,118,101,114,116,101,120,93,10,10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,108,97,121,111,117,116,40,108,111,99,97,116,105,111,110,32,61,32,48,41,32,111,117,116,32,118,101,99,51,32,117,118,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,10,35,105,102,100,101,102,32,77,79,68,69,95,76,73,78,69,83,10,9,117,118,95,105,110,116,101,114,112,32,61,32,118,101,99,51,40,48,44,48,44,49,41,59,10,35,101,110,100,105,102,10,125,10,10,35,91,102,114,97,103,109,101,110,116,93,10,10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,35,100,101,102,105,110,101,32,77,95,80,73,32,51,46,49,52,49,53,57,50,54,53,51,53,57,10,10,108,97,121,111,117,116,40,108,111,99,97,116,105,111,110,32,61,32,48,41,32,111,117,116,32,118,101,99,52,32,100,115,116,95,99,111,108,111,114,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,100,115,116,95,99,111,108,111,114,32,61,32,118,101,99,52,40,49,44,49,44,48,44,48,41,59,10,125,10,0
+R"<!>(#[versions]
+
+lines = "#define MODE_LINES";
+
+#[vertex]
+
+#version 450
+
+#VERSION_DEFINES
+
+layout(location = 0) out vec3 uv_interp;
+
+void main() {
+
+#ifdef MODE_LINES
+ uv_interp = vec3(0,0,1);
+#endif
+}
+
+#[fragment]
+
+#version 450
+
+#VERSION_DEFINES
+
+#define M_PI 3.14159265359
+
+layout(location = 0) out vec4 dst_color;
+
+void main() {
+ dst_color = vec4(1,1,0,0);
+}
+)<!>"
};
#endif
diff --git a/tests/python_build/fixtures/rd_glsl/compute_expected_full.glsl b/tests/python_build/fixtures/rd_glsl/compute_expected_full.glsl
index b59923e28a..1184510020 100644
--- a/tests/python_build/fixtures/rd_glsl/compute_expected_full.glsl
+++ b/tests/python_build/fixtures/rd_glsl/compute_expected_full.glsl
@@ -11,7 +11,19 @@ public:
ComputeShaderRD() {
static const char _compute_code[] = {
-10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,35,100,101,102,105,110,101,32,66,76,79,67,75,95,83,73,90,69,32,56,10,10,35,100,101,102,105,110,101,32,77,95,80,73,32,51,46,49,52,49,53,57,50,54,53,51,53,57,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,117,105,110,116,32,116,32,61,32,66,76,79,67,75,95,83,73,90,69,32,43,32,49,59,10,125,10,0
+R"<!>(
+#version 450
+
+#VERSION_DEFINES
+
+#define BLOCK_SIZE 8
+
+#define M_PI 3.14159265359
+
+void main() {
+ uint t = BLOCK_SIZE + 1;
+}
+)<!>"
};
setup(nullptr, nullptr, _compute_code, "ComputeShaderRD");
}
diff --git a/tests/python_build/fixtures/rd_glsl/vertex_fragment_expected_full.glsl b/tests/python_build/fixtures/rd_glsl/vertex_fragment_expected_full.glsl
index ff804dbf89..2f809f1bfe 100644
--- a/tests/python_build/fixtures/rd_glsl/vertex_fragment_expected_full.glsl
+++ b/tests/python_build/fixtures/rd_glsl/vertex_fragment_expected_full.glsl
@@ -11,10 +11,33 @@ public:
VertexFragmentShaderRD() {
static const char _vertex_code[] = {
-10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,35,100,101,102,105,110,101,32,77,95,80,73,32,51,46,49,52,49,53,57,50,54,53,51,53,57,10,10,108,97,121,111,117,116,40,108,111,99,97,116,105,111,110,32,61,32,48,41,32,111,117,116,32,118,101,99,50,32,117,118,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,117,118,95,105,110,116,101,114,112,32,61,32,118,101,99,50,40,48,44,32,49,41,59,10,125,10,10,0
+R"<!>(
+#version 450
+
+#VERSION_DEFINES
+
+#define M_PI 3.14159265359
+
+layout(location = 0) out vec2 uv_interp;
+
+void main() {
+ uv_interp = vec2(0, 1);
+}
+
+)<!>"
};
static const char _fragment_code[] = {
-10,35,118,101,114,115,105,111,110,32,52,53,48,10,10,35,86,69,82,83,73,79,78,95,68,69,70,73,78,69,83,10,10,108,97,121,111,117,116,40,108,111,99,97,116,105,111,110,32,61,32,48,41,32,105,110,32,118,101,99,50,32,117,118,95,105,110,116,101,114,112,59,10,10,118,111,105,100,32,109,97,105,110,40,41,32,123,10,9,117,118,95,105,110,116,101,114,112,32,61,32,118,101,99,50,40,49,44,32,48,41,59,10,125,10,0
+R"<!>(
+#version 450
+
+#VERSION_DEFINES
+
+layout(location = 0) in vec2 uv_interp;
+
+void main() {
+ uv_interp = vec2(1, 0);
+}
+)<!>"
};
setup(_vertex_code, _fragment_code, nullptr, "VertexFragmentShaderRD");
}
diff --git a/tests/scene/test_audio_stream_wav.h b/tests/scene/test_audio_stream_wav.h
index e8f3c9e8f5..5166cd3c13 100644
--- a/tests/scene/test_audio_stream_wav.h
+++ b/tests/scene/test_audio_stream_wav.h
@@ -159,6 +159,8 @@ void run_test(String file_name, AudioStreamWAV::Format data_format, bool stereo,
for (const ResourceImporter::ImportOption &E : options_list) {
options_map[E.option.name] = E.default_value;
}
+ // Compressed streams can't be saved, disable compression.
+ options_map["compress/mode"] = 0;
REQUIRE(wav_importer->import(save_path, save_path, options_map, nullptr) == OK);
diff --git a/tests/scene/test_button.h b/tests/scene/test_button.h
new file mode 100644
index 0000000000..55097edc95
--- /dev/null
+++ b/tests/scene/test_button.h
@@ -0,0 +1,66 @@
+/**************************************************************************/
+/* test_button.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_BUTTON_H
+#define TEST_BUTTON_H
+
+#include "scene/gui/button.h"
+#include "scene/main/window.h"
+
+#include "tests/test_macros.h"
+
+namespace TestButton {
+TEST_CASE("[SceneTree][Button] is_hovered()") {
+ // Create new button instance.
+ Button *button = memnew(Button);
+ CHECK(button != nullptr);
+ Window *root = SceneTree::get_singleton()->get_root();
+ root->add_child(button);
+
+ // Set up button's size and position.
+ button->set_size(Size2i(50, 50));
+ button->set_position(Size2i(10, 10));
+
+ // Button should initially be not hovered.
+ CHECK(button->is_hovered() == false);
+
+ // Simulate mouse entering the button.
+ SEND_GUI_MOUSE_MOTION_EVENT(Point2i(25, 25), MouseButtonMask::NONE, Key::NONE);
+ CHECK(button->is_hovered() == true);
+
+ // Simulate mouse exiting the button.
+ SEND_GUI_MOUSE_MOTION_EVENT(Point2i(150, 150), MouseButtonMask::NONE, Key::NONE);
+ CHECK(button->is_hovered() == false);
+
+ memdelete(button);
+}
+
+} //namespace TestButton
+#endif // TEST_BUTTON_H
diff --git a/tests/scene/test_gradient_texture.h b/tests/scene/test_gradient_texture.h
new file mode 100644
index 0000000000..16a92fbe4a
--- /dev/null
+++ b/tests/scene/test_gradient_texture.h
@@ -0,0 +1,87 @@
+/**************************************************************************/
+/* test_gradient_texture.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_GRADIENT_TEXTURE_H
+#define TEST_GRADIENT_TEXTURE_H
+
+#include "scene/resources/gradient_texture.h"
+
+#include "tests/test_macros.h"
+
+namespace TestGradientTexture {
+
+// [SceneTree] in a test case name enables initializing a mock render server,
+// which ImageTexture is dependent on.
+TEST_CASE("[SceneTree][GradientTexture1D] Create GradientTexture1D") {
+ Ref<GradientTexture1D> gradient_texture = memnew(GradientTexture1D);
+
+ Ref<Gradient> test_gradient = memnew(Gradient);
+ gradient_texture->set_gradient(test_gradient);
+ CHECK(gradient_texture->get_gradient() == test_gradient);
+
+ gradient_texture->set_width(83);
+ CHECK(gradient_texture->get_width() == 83);
+
+ gradient_texture->set_use_hdr(true);
+ CHECK(gradient_texture->is_using_hdr());
+}
+
+TEST_CASE("[SceneTree][GradientTexture2D] Create GradientTexture2D") {
+ Ref<GradientTexture2D> gradient_texture = memnew(GradientTexture2D);
+
+ Ref<Gradient> test_gradient = memnew(Gradient);
+ gradient_texture->set_gradient(test_gradient);
+ CHECK(gradient_texture->get_gradient() == test_gradient);
+
+ gradient_texture->set_width(82);
+ CHECK(gradient_texture->get_width() == 82);
+
+ gradient_texture->set_height(81);
+ CHECK(gradient_texture->get_height() == 81);
+
+ gradient_texture->set_use_hdr(true);
+ CHECK(gradient_texture->is_using_hdr());
+
+ gradient_texture->set_fill(GradientTexture2D::Fill::FILL_SQUARE);
+ CHECK(gradient_texture->get_fill() == GradientTexture2D::Fill::FILL_SQUARE);
+
+ gradient_texture->set_fill_from(Vector2(0.2, 0.25));
+ CHECK(gradient_texture->get_fill_from() == Vector2(0.2, 0.25));
+
+ gradient_texture->set_fill_to(Vector2(0.35, 0.5));
+ CHECK(gradient_texture->get_fill_to() == Vector2(0.35, 0.5));
+
+ gradient_texture->set_repeat(GradientTexture2D::Repeat::REPEAT);
+ CHECK(gradient_texture->get_repeat() == GradientTexture2D::Repeat::REPEAT);
+}
+
+} //namespace TestGradientTexture
+
+#endif // TEST_GRADIENT_TEXTURE_H
diff --git a/tests/scene/test_node_2d.h b/tests/scene/test_node_2d.h
index 8cf6408438..e8e7b2880d 100644
--- a/tests/scene/test_node_2d.h
+++ b/tests/scene/test_node_2d.h
@@ -86,6 +86,131 @@ TEST_CASE("[SceneTree][Node2D]") {
}
}
+TEST_CASE("[SceneTree][Node2D] Utility methods") {
+ Node2D *test_node1 = memnew(Node2D);
+ Node2D *test_node2 = memnew(Node2D);
+ Node2D *test_node3 = memnew(Node2D);
+ Node2D *test_sibling = memnew(Node2D);
+ SceneTree::get_singleton()->get_root()->add_child(test_node1);
+
+ test_node1->set_position(Point2(100, 100));
+ test_node1->set_rotation(Math::deg_to_rad(30.0));
+ test_node1->set_scale(Size2(1, 1));
+ test_node1->add_child(test_node2);
+
+ test_node2->set_position(Point2(10, 0));
+ test_node2->set_rotation(Math::deg_to_rad(60.0));
+ test_node2->set_scale(Size2(1, 1));
+ test_node2->add_child(test_node3);
+ test_node2->add_child(test_sibling);
+
+ test_node3->set_position(Point2(0, 10));
+ test_node3->set_rotation(Math::deg_to_rad(0.0));
+ test_node3->set_scale(Size2(2, 2));
+
+ test_sibling->set_position(Point2(5, 10));
+ test_sibling->set_rotation(Math::deg_to_rad(90.0));
+ test_sibling->set_scale(Size2(2, 1));
+
+ SUBCASE("[Node2D] look_at") {
+ test_node3->look_at(Vector2(1, 1));
+
+ CHECK(test_node3->get_global_position().is_equal_approx(Point2(98.66026, 105)));
+ CHECK(Math::is_equal_approx(test_node3->get_global_rotation(), real_t(-2.32477)));
+ CHECK(test_node3->get_global_scale().is_equal_approx(Vector2(2, 2)));
+
+ CHECK(test_node3->get_position().is_equal_approx(Vector2(0, 10)));
+ CHECK(Math::is_equal_approx(test_node3->get_rotation(), real_t(2.38762)));
+ CHECK(test_node3->get_scale().is_equal_approx(Vector2(2, 2)));
+
+ test_node3->look_at(Vector2(0, 10));
+
+ CHECK(test_node3->get_global_position().is_equal_approx(Vector2(98.66026, 105)));
+ CHECK(Math::is_equal_approx(test_node3->get_global_rotation(), real_t(-2.37509)));
+ CHECK(test_node3->get_global_scale().is_equal_approx(Vector2(2, 2)));
+
+ CHECK(test_node3->get_position().is_equal_approx(Vector2(0, 10)));
+ CHECK(Math::is_equal_approx(test_node3->get_rotation(), real_t(2.3373)));
+ CHECK(test_node3->get_scale().is_equal_approx(Vector2(2, 2)));
+
+ // Don't do anything if look_at own position.
+ test_node3->look_at(test_node3->get_global_position());
+
+ CHECK(test_node3->get_global_position().is_equal_approx(Vector2(98.66026, 105)));
+ CHECK(Math::is_equal_approx(test_node3->get_global_rotation(), real_t(-2.37509)));
+ CHECK(test_node3->get_global_scale().is_equal_approx(Vector2(2, 2)));
+
+ CHECK(test_node3->get_position().is_equal_approx(Vector2(0, 10)));
+ CHECK(Math::is_equal_approx(test_node3->get_rotation(), real_t(2.3373)));
+ CHECK(test_node3->get_scale().is_equal_approx(Vector2(2, 2)));
+
+ // Revert any rotation caused by look_at, must run after look_at tests
+ test_node3->set_rotation(Math::deg_to_rad(0.0));
+ }
+
+ SUBCASE("[Node2D] get_angle_to") {
+ CHECK(Math::is_equal_approx(test_node3->get_angle_to(Vector2(1, 1)), real_t(2.38762)));
+ CHECK(Math::is_equal_approx(test_node3->get_angle_to(Vector2(0, 10)), real_t(2.3373)));
+ CHECK(Math::is_equal_approx(test_node3->get_angle_to(Vector2(2, -5)), real_t(2.42065)));
+ CHECK(Math::is_equal_approx(test_node3->get_angle_to(Vector2(-2, 5)), real_t(2.3529)));
+
+ // Return 0 when get_angle_to own position.
+ CHECK(Math::is_equal_approx(test_node3->get_angle_to(test_node3->get_global_position()), real_t(0)));
+ }
+
+ SUBCASE("[Node2D] to_local") {
+ Point2 node3_local = test_node3->to_local(Point2(1, 2));
+ CHECK(node3_local.is_equal_approx(Point2(-51.5, 48.83013)));
+
+ node3_local = test_node3->to_local(Point2(-2, 1));
+ CHECK(node3_local.is_equal_approx(Point2(-52, 50.33013)));
+
+ node3_local = test_node3->to_local(Point2(0, 0));
+ CHECK(node3_local.is_equal_approx(Point2(-52.5, 49.33013)));
+
+ node3_local = test_node3->to_local(test_node3->get_global_position());
+ CHECK(node3_local.is_equal_approx(Point2(0, 0)));
+ }
+
+ SUBCASE("[Node2D] to_global") {
+ Point2 node3_global = test_node3->to_global(Point2(1, 2));
+ CHECK(node3_global.is_equal_approx(Point2(94.66026, 107)));
+
+ node3_global = test_node3->to_global(Point2(-2, 1));
+ CHECK(node3_global.is_equal_approx(Point2(96.66026, 101)));
+
+ node3_global = test_node3->to_global(Point2(0, 0));
+ CHECK(node3_global.is_equal_approx(test_node3->get_global_position()));
+ }
+
+ SUBCASE("[Node2D] get_relative_transform_to_parent") {
+ Transform2D relative_xform = test_node3->get_relative_transform_to_parent(test_node3);
+ CHECK(relative_xform.is_equal_approx(Transform2D()));
+
+ relative_xform = test_node3->get_relative_transform_to_parent(test_node2);
+ CHECK(relative_xform.get_origin().is_equal_approx(Vector2(0, 10)));
+ CHECK(Math::is_equal_approx(relative_xform.get_rotation(), real_t(0)));
+ CHECK(relative_xform.get_scale().is_equal_approx(Vector2(2, 2)));
+
+ relative_xform = test_node3->get_relative_transform_to_parent(test_node1);
+ CHECK(relative_xform.get_origin().is_equal_approx(Vector2(1.339746, 5)));
+ CHECK(Math::is_equal_approx(relative_xform.get_rotation(), real_t(1.0472)));
+ CHECK(relative_xform.get_scale().is_equal_approx(Vector2(2, 2)));
+
+ ERR_PRINT_OFF;
+ // In case of a sibling all transforms until the root are accumulated.
+ Transform2D xform = test_node3->get_relative_transform_to_parent(test_sibling);
+ Transform2D return_xform = test_node1->get_global_transform().inverse() * test_node3->get_global_transform();
+ CHECK(xform.is_equal_approx(return_xform));
+ ERR_PRINT_ON;
+ }
+
+ memdelete(test_sibling);
+ memdelete(test_node3);
+ memdelete(test_node2);
+ memdelete(test_node1);
+}
+
} // namespace TestNode2D
#endif // TEST_NODE_2D_H
diff --git a/tests/scene/test_option_button.h b/tests/scene/test_option_button.h
new file mode 100644
index 0000000000..56c1c7d611
--- /dev/null
+++ b/tests/scene/test_option_button.h
@@ -0,0 +1,136 @@
+/**************************************************************************/
+/* test_option_button.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_OPTION_BUTTON_H
+#define TEST_OPTION_BUTTON_H
+
+#include "scene/gui/option_button.h"
+
+#include "tests/test_macros.h"
+
+namespace TestOptionButton {
+
+TEST_CASE("[SceneTree][OptionButton] Initialization") {
+ OptionButton *test_opt = memnew(OptionButton);
+
+ SUBCASE("There should be no options right after initialization") {
+ CHECK_FALSE(test_opt->has_selectable_items());
+ CHECK(test_opt->get_item_count() == 0);
+ }
+
+ memdelete(test_opt);
+}
+
+TEST_CASE("[SceneTree][OptionButton] Single item") {
+ OptionButton *test_opt = memnew(OptionButton);
+
+ SUBCASE("There should a single item after after adding one") {
+ test_opt->add_item("single", 1013);
+
+ CHECK(test_opt->has_selectable_items());
+ CHECK(test_opt->get_item_count() == 1);
+ CHECK(test_opt->get_item_index(1013) == 0);
+ CHECK(test_opt->get_item_id(0) == 1013);
+
+ test_opt->remove_item(0);
+
+ CHECK_FALSE(test_opt->has_selectable_items());
+ CHECK(test_opt->get_item_count() == 0);
+ }
+
+ SUBCASE("There should a single item after after adding an icon") {
+ Ref<Texture2D> test_icon = memnew(Texture2D);
+ test_opt->add_icon_item(test_icon, "icon", 345);
+
+ CHECK(test_opt->has_selectable_items());
+ CHECK(test_opt->get_item_count() == 1);
+ CHECK(test_opt->get_item_index(345) == 0);
+ CHECK(test_opt->get_item_id(0) == 345);
+
+ test_opt->remove_item(0);
+
+ CHECK_FALSE(test_opt->has_selectable_items());
+ CHECK(test_opt->get_item_count() == 0);
+ }
+
+ memdelete(test_opt);
+}
+
+TEST_CASE("[SceneTree][OptionButton] Complex structure") {
+ OptionButton *test_opt = memnew(OptionButton);
+
+ SUBCASE("Creating a complex structure and checking getters") {
+ // Regular item at index 0.
+ Ref<Texture2D> test_icon1 = memnew(Texture2D);
+ Ref<Texture2D> test_icon2 = memnew(Texture2D);
+ // Regular item at index 3.
+ Ref<Texture2D> test_icon4 = memnew(Texture2D);
+
+ test_opt->add_item("first", 100);
+ test_opt->add_icon_item(test_icon1, "second_icon", 101);
+ test_opt->add_icon_item(test_icon2, "third_icon", 102);
+ test_opt->add_item("fourth", 104);
+ test_opt->add_icon_item(test_icon4, "fifth_icon", 104);
+
+ // Disable test_icon4.
+ test_opt->set_item_disabled(4, true);
+
+ CHECK(test_opt->has_selectable_items());
+ CHECK(test_opt->get_item_count() == 5);
+
+ // Check for test_icon2.
+ CHECK(test_opt->get_item_index(102) == 2);
+ CHECK(test_opt->get_item_id(2) == 102);
+
+ // Remove the two regular items.
+ test_opt->remove_item(3);
+ test_opt->remove_item(0);
+
+ CHECK(test_opt->has_selectable_items());
+ CHECK(test_opt->get_item_count() == 3);
+
+ // Check test_icon4.
+ CHECK(test_opt->get_item_index(104) == 2);
+ CHECK(test_opt->get_item_id(2) == 104);
+
+ // Remove the two non-disabled icon items.
+ test_opt->remove_item(1);
+ test_opt->remove_item(0);
+
+ CHECK_FALSE(test_opt->has_selectable_items());
+ CHECK(test_opt->get_item_count() == 1);
+ }
+
+ memdelete(test_opt);
+}
+
+} // namespace TestOptionButton
+
+#endif // TEST_OPTION_BUTTON_H
diff --git a/tests/scene/test_style_box_texture.h b/tests/scene/test_style_box_texture.h
new file mode 100644
index 0000000000..cc5be4b2d4
--- /dev/null
+++ b/tests/scene/test_style_box_texture.h
@@ -0,0 +1,194 @@
+/**************************************************************************/
+/* test_style_box_texture.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_STYLE_BOX_TEXTURE_H
+#define TEST_STYLE_BOX_TEXTURE_H
+
+#include "scene/resources/style_box_texture.h"
+
+#include "tests/test_macros.h"
+
+namespace TestStyleBoxTexture {
+
+TEST_CASE("[StyleBoxTexture] Constructor") {
+ Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
+
+ CHECK(style_box_texture->get_h_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_STRETCH);
+ CHECK(style_box_texture->get_v_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_STRETCH);
+ CHECK(style_box_texture->is_draw_center_enabled() == true);
+
+ CHECK(style_box_texture->get_expand_margin(SIDE_LEFT) == 0);
+ CHECK(style_box_texture->get_expand_margin(SIDE_TOP) == 0);
+ CHECK(style_box_texture->get_expand_margin(SIDE_RIGHT) == 0);
+ CHECK(style_box_texture->get_expand_margin(SIDE_BOTTOM) == 0);
+
+ CHECK(style_box_texture->get_modulate() == Color(1, 1, 1, 1));
+ CHECK(style_box_texture->get_region_rect() == Rect2(0, 0, 0, 0));
+ CHECK(style_box_texture->get_texture() == Ref<Texture2D>());
+
+ CHECK(style_box_texture->get_texture_margin(SIDE_LEFT) == 0);
+ CHECK(style_box_texture->get_texture_margin(SIDE_TOP) == 0);
+ CHECK(style_box_texture->get_texture_margin(SIDE_RIGHT) == 0);
+ CHECK(style_box_texture->get_texture_margin(SIDE_BOTTOM) == 0);
+}
+
+TEST_CASE("[StyleBoxTexture] set_texture, get_texture") {
+ Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
+ Ref<Texture2D> texture = memnew(Texture2D);
+
+ style_box_texture->set_texture(texture);
+ CHECK(style_box_texture->get_texture() == texture);
+}
+
+TEST_CASE("[StyleBoxTexture] set_texture_margin, set_texture_margin_all, set_texture_margin_individual, get_texture_margin") {
+ Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
+
+ SUBCASE("set_texture_margin, get_texture_margin") {
+ style_box_texture->set_texture_margin(SIDE_LEFT, 1);
+ style_box_texture->set_texture_margin(SIDE_TOP, 1);
+ style_box_texture->set_texture_margin(SIDE_RIGHT, 1);
+ style_box_texture->set_texture_margin(SIDE_BOTTOM, 1);
+
+ CHECK(style_box_texture->get_texture_margin(SIDE_LEFT) == 1);
+ CHECK(style_box_texture->get_texture_margin(SIDE_TOP) == 1);
+ CHECK(style_box_texture->get_texture_margin(SIDE_RIGHT) == 1);
+ CHECK(style_box_texture->get_texture_margin(SIDE_BOTTOM) == 1);
+ }
+
+ SUBCASE("set_texture_margin_all") {
+ style_box_texture->set_texture_margin_all(2);
+
+ CHECK(style_box_texture->get_texture_margin(SIDE_LEFT) == 2);
+ CHECK(style_box_texture->get_texture_margin(SIDE_TOP) == 2);
+ CHECK(style_box_texture->get_texture_margin(SIDE_RIGHT) == 2);
+ CHECK(style_box_texture->get_texture_margin(SIDE_BOTTOM) == 2);
+ }
+
+ SUBCASE("set_texture_margin_individual") {
+ style_box_texture->set_texture_margin_individual(3, 4, 5, 6);
+
+ CHECK(style_box_texture->get_texture_margin(SIDE_LEFT) == 3);
+ CHECK(style_box_texture->get_texture_margin(SIDE_TOP) == 4);
+ CHECK(style_box_texture->get_texture_margin(SIDE_RIGHT) == 5);
+ CHECK(style_box_texture->get_texture_margin(SIDE_BOTTOM) == 6);
+ }
+}
+
+TEST_CASE("[StyleBoxTexture] set_expand_margin, set_expand_margin_all, set_expand_margin_individual") {
+ Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
+
+ SUBCASE("set_expand_margin, get_expand_margin") {
+ style_box_texture->set_expand_margin(SIDE_LEFT, 1);
+ style_box_texture->set_expand_margin(SIDE_TOP, 1);
+ style_box_texture->set_expand_margin(SIDE_RIGHT, 1);
+ style_box_texture->set_expand_margin(SIDE_BOTTOM, 1);
+
+ CHECK(style_box_texture->get_expand_margin(SIDE_LEFT) == 1);
+ CHECK(style_box_texture->get_expand_margin(SIDE_TOP) == 1);
+ CHECK(style_box_texture->get_expand_margin(SIDE_RIGHT) == 1);
+ CHECK(style_box_texture->get_expand_margin(SIDE_BOTTOM) == 1);
+ }
+
+ SUBCASE("set_expand_margin_all") {
+ style_box_texture->set_expand_margin_all(2);
+
+ CHECK(style_box_texture->get_expand_margin(SIDE_LEFT) == 2);
+ CHECK(style_box_texture->get_expand_margin(SIDE_TOP) == 2);
+ CHECK(style_box_texture->get_expand_margin(SIDE_RIGHT) == 2);
+ CHECK(style_box_texture->get_expand_margin(SIDE_BOTTOM) == 2);
+ }
+
+ SUBCASE("set_expand_margin_individual") {
+ style_box_texture->set_expand_margin_individual(3, 4, 5, 6);
+
+ CHECK(style_box_texture->get_expand_margin(SIDE_LEFT) == 3);
+ CHECK(style_box_texture->get_expand_margin(SIDE_TOP) == 4);
+ CHECK(style_box_texture->get_expand_margin(SIDE_RIGHT) == 5);
+ CHECK(style_box_texture->get_expand_margin(SIDE_BOTTOM) == 6);
+ }
+}
+
+TEST_CASE("[StyleBoxTexture] set_region_rect, get_region_rect") {
+ Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
+
+ style_box_texture->set_region_rect(Rect2(1, 1, 1, 1));
+ CHECK(style_box_texture->get_region_rect() == Rect2(1, 1, 1, 1));
+}
+
+TEST_CASE("[StyleBoxTexture] set_draw_center, get_draw_center") {
+ Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
+
+ style_box_texture->set_draw_center(false);
+ CHECK(style_box_texture->is_draw_center_enabled() == false);
+}
+
+TEST_CASE("[StyleBoxTexture] set_h_axis_stretch_mode, set_v_axis_stretch_mode, get_h_axis_stretch_mode, get_v_axis_stretch_mode") {
+ Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
+
+ SUBCASE("set_h_axis_stretch_mode, get_h_axis_stretch_mode") {
+ style_box_texture->set_h_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_TILE);
+ CHECK(style_box_texture->get_h_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_TILE);
+
+ style_box_texture->set_h_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_TILE_FIT);
+ CHECK(style_box_texture->get_h_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_TILE_FIT);
+
+ style_box_texture->set_h_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_STRETCH);
+ CHECK(style_box_texture->get_h_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_STRETCH);
+ }
+
+ SUBCASE("set_v_axis_stretch_mode, get_v_axis_stretch_mode") {
+ style_box_texture->set_v_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_TILE);
+ CHECK(style_box_texture->get_v_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_TILE);
+
+ style_box_texture->set_v_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_TILE_FIT);
+ CHECK(style_box_texture->get_v_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_TILE_FIT);
+
+ style_box_texture->set_v_axis_stretch_mode(style_box_texture->AXIS_STRETCH_MODE_STRETCH);
+ CHECK(style_box_texture->get_v_axis_stretch_mode() == style_box_texture->AXIS_STRETCH_MODE_STRETCH);
+ }
+}
+
+TEST_CASE("[StyleBoxTexture] set_modulate, get_modulate") {
+ Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
+
+ style_box_texture->set_modulate(Color(0, 0, 0, 0));
+ CHECK(style_box_texture->get_modulate() == Color(0, 0, 0, 0));
+}
+
+TEST_CASE("[StyleBoxTexture] get_draw_rect") {
+ Ref<StyleBoxTexture> style_box_texture = memnew(StyleBoxTexture);
+
+ style_box_texture->set_expand_margin_all(5);
+ CHECK(style_box_texture->get_draw_rect(Rect2(0, 0, 1, 1)) == Rect2(-5, -5, 11, 11));
+}
+
+} // namespace TestStyleBoxTexture
+
+#endif // TEST_STYLE_BOX_TEXTURE_H
diff --git a/tests/scene/test_tree.h b/tests/scene/test_tree.h
new file mode 100644
index 0000000000..e19f8311e2
--- /dev/null
+++ b/tests/scene/test_tree.h
@@ -0,0 +1,175 @@
+/**************************************************************************/
+/* test_tree.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_TREE_H
+#define TEST_TREE_H
+
+#include "scene/gui/tree.h"
+
+#include "tests/test_macros.h"
+
+namespace TestTree {
+
+TEST_CASE("[SceneTree][Tree]") {
+ SUBCASE("[Tree] Create and remove items.") {
+ Tree *tree = memnew(Tree);
+ TreeItem *root = tree->create_item();
+
+ TreeItem *child1 = tree->create_item();
+ CHECK_EQ(root->get_child_count(), 1);
+
+ TreeItem *child2 = tree->create_item(root);
+ CHECK_EQ(root->get_child_count(), 2);
+
+ TreeItem *child3 = tree->create_item(root, 0);
+ CHECK_EQ(root->get_child_count(), 3);
+
+ CHECK_EQ(root->get_child(0), child3);
+ CHECK_EQ(root->get_child(1), child1);
+ CHECK_EQ(root->get_child(2), child2);
+
+ root->remove_child(child3);
+ CHECK_EQ(root->get_child_count(), 2);
+
+ root->add_child(child3);
+ CHECK_EQ(root->get_child_count(), 3);
+
+ TreeItem *child4 = root->create_child();
+ CHECK_EQ(root->get_child_count(), 4);
+
+ CHECK_EQ(root->get_child(0), child1);
+ CHECK_EQ(root->get_child(1), child2);
+ CHECK_EQ(root->get_child(2), child3);
+ CHECK_EQ(root->get_child(3), child4);
+
+ memdelete(tree);
+ }
+
+ SUBCASE("[Tree] Clear items.") {
+ Tree *tree = memnew(Tree);
+ TreeItem *root = tree->create_item();
+
+ for (int i = 0; i < 10; i++) {
+ tree->create_item();
+ }
+ CHECK_EQ(root->get_child_count(), 10);
+
+ root->clear_children();
+ CHECK_EQ(root->get_child_count(), 0);
+
+ memdelete(tree);
+ }
+
+ SUBCASE("[Tree] Get last item.") {
+ Tree *tree = memnew(Tree);
+ TreeItem *root = tree->create_item();
+
+ TreeItem *last;
+ for (int i = 0; i < 10; i++) {
+ last = tree->create_item();
+ }
+ CHECK_EQ(root->get_child_count(), 10);
+ CHECK_EQ(tree->get_last_item(), last);
+
+ // Check nested.
+ TreeItem *old_last = last;
+ for (int i = 0; i < 10; i++) {
+ last = tree->create_item(old_last);
+ }
+ CHECK_EQ(tree->get_last_item(), last);
+
+ memdelete(tree);
+ }
+
+ // https://github.com/godotengine/godot/issues/96205
+ SUBCASE("[Tree] Get last item after removal.") {
+ Tree *tree = memnew(Tree);
+ TreeItem *root = tree->create_item();
+
+ TreeItem *child1 = tree->create_item(root);
+ TreeItem *child2 = tree->create_item(root);
+
+ CHECK_EQ(root->get_child_count(), 2);
+ CHECK_EQ(tree->get_last_item(), child2);
+
+ root->remove_child(child2);
+
+ CHECK_EQ(root->get_child_count(), 1);
+ CHECK_EQ(tree->get_last_item(), child1);
+
+ root->add_child(child2);
+
+ CHECK_EQ(root->get_child_count(), 2);
+ CHECK_EQ(tree->get_last_item(), child2);
+
+ memdelete(tree);
+ }
+
+ SUBCASE("[Tree] Previous and Next items.") {
+ Tree *tree = memnew(Tree);
+ TreeItem *root = tree->create_item();
+
+ TreeItem *child1 = tree->create_item();
+ TreeItem *child2 = tree->create_item();
+ TreeItem *child3 = tree->create_item();
+ CHECK_EQ(child1->get_next(), child2);
+ CHECK_EQ(child1->get_next_in_tree(), child2);
+ CHECK_EQ(child2->get_next(), child3);
+ CHECK_EQ(child2->get_next_in_tree(), child3);
+ CHECK_EQ(child3->get_next(), nullptr);
+ CHECK_EQ(child3->get_next_in_tree(), nullptr);
+
+ CHECK_EQ(child1->get_prev(), nullptr);
+ CHECK_EQ(child1->get_prev_in_tree(), root);
+ CHECK_EQ(child2->get_prev(), child1);
+ CHECK_EQ(child2->get_prev_in_tree(), child1);
+ CHECK_EQ(child3->get_prev(), child2);
+ CHECK_EQ(child3->get_prev_in_tree(), child2);
+
+ TreeItem *nested1 = tree->create_item(child2);
+ TreeItem *nested2 = tree->create_item(child2);
+ TreeItem *nested3 = tree->create_item(child2);
+
+ CHECK_EQ(child1->get_next(), child2);
+ CHECK_EQ(child1->get_next_in_tree(), child2);
+ CHECK_EQ(child2->get_next(), child3);
+ CHECK_EQ(child2->get_next_in_tree(), nested1);
+ CHECK_EQ(child3->get_prev(), child2);
+ CHECK_EQ(child3->get_prev_in_tree(), nested3);
+ CHECK_EQ(nested1->get_prev_in_tree(), child2);
+ CHECK_EQ(nested1->get_next_in_tree(), nested2);
+
+ memdelete(tree);
+ }
+}
+
+} // namespace TestTree
+
+#endif // TEST_TREE_H
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index edadc52a16..7e1c431a3c 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -48,6 +48,7 @@
#include "tests/core/io/test_image.h"
#include "tests/core/io/test_ip.h"
#include "tests/core/io/test_json.h"
+#include "tests/core/io/test_json_native.h"
#include "tests/core/io/test_marshalls.h"
#include "tests/core/io/test_pck_packer.h"
#include "tests/core/io/test_resource.h"
@@ -104,12 +105,14 @@
#include "tests/scene/test_animation.h"
#include "tests/scene/test_audio_stream_wav.h"
#include "tests/scene/test_bit_map.h"
+#include "tests/scene/test_button.h"
#include "tests/scene/test_camera_2d.h"
#include "tests/scene/test_control.h"
#include "tests/scene/test_curve.h"
#include "tests/scene/test_curve_2d.h"
#include "tests/scene/test_curve_3d.h"
#include "tests/scene/test_gradient.h"
+#include "tests/scene/test_gradient_texture.h"
#include "tests/scene/test_image_texture.h"
#include "tests/scene/test_image_texture_3d.h"
#include "tests/scene/test_instance_placeholder.h"
@@ -119,6 +122,7 @@
#include "tests/scene/test_path_2d.h"
#include "tests/scene/test_path_follow_2d.h"
#include "tests/scene/test_sprite_frames.h"
+#include "tests/scene/test_style_box_texture.h"
#include "tests/scene/test_theme.h"
#include "tests/scene/test_timer.h"
#include "tests/scene/test_viewport.h"
@@ -132,7 +136,9 @@
#include "tests/scene/test_code_edit.h"
#include "tests/scene/test_color_picker.h"
#include "tests/scene/test_graph_node.h"
+#include "tests/scene/test_option_button.h"
#include "tests/scene/test_text_edit.h"
+#include "tests/scene/test_tree.h"
#endif // ADVANCED_GUI_DISABLED
#ifndef _3D_DISABLED
diff --git a/thirdparty/README.md b/thirdparty/README.md
index a71c189fca..f8b41a8c52 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -543,7 +543,7 @@ in the MSVC debugger.
## mbedtls
- Upstream: https://github.com/Mbed-TLS/mbedtls
-- Version: 3.6.0 (2ca6c285a0dd3f33982dd57299012dacab1ff206, 2024)
+- Version: 3.6.1 (71c569d44bf3a8bd53d874c81ee8ac644dd6e9e3, 2024)
- License: Apache 2.0
File extracted from upstream release tarball:
@@ -553,8 +553,6 @@ File extracted from upstream release tarball:
- All `.c` and `.h` from `library/` to `thirdparty/mbedtls/library/` except
for the `psa_*.c` source files
- The `LICENSE` file (edited to keep only the Apache 2.0 variant)
-- Applied the patch `no-flexible-arrays.diff` to fix Windows build (see
- upstream GH-9020)
- Applied the patch `msvc-redeclaration-bug.diff` to fix a compilation error
with some MSVC versions
- Added 2 files `godot_core_mbedtls_platform.c` and `godot_core_mbedtls_config.h`
@@ -692,8 +690,8 @@ Collection of single-file libraries used in Godot components.
* License: MIT
- `qoa.h`
* Upstream: https://github.com/phoboslab/qoa
- * Version: git (5c2a86d615661f34636cf179abf4fa278d3257e0, 2024)
- * Modifications: Inlined functions, patched uninitialized variables and untyped mallocs.
+ * Version: git (e0c69447d4d3945c3c92ac1751e4cdc9803a8303, 2024)
+ * Modifications: Added a few modifiers to comply with C++ nature.
* License: MIT
- `r128.{c,h}`
* Upstream: https://github.com/fahickman/r128
@@ -909,7 +907,7 @@ instead of `miniz.h` as an external dependency.
## thorvg
- Upstream: https://github.com/thorvg/thorvg
-- Version: 0.14.2 (f6c4d8a94e0b2194fe911d6e19a550683055dd50, 2024)
+- Version: 0.14.7 (e3a6bf5229a9671c385ee78bc33e6e6b611a9729, 2024)
- License: MIT
Files extracted from upstream source:
diff --git a/thirdparty/libwebp/patches/godot-clang-cl-fix.patch b/thirdparty/libwebp/patches/godot-clang-cl-fix.patch
new file mode 100644
index 0000000000..ee4f598951
--- /dev/null
+++ b/thirdparty/libwebp/patches/godot-clang-cl-fix.patch
@@ -0,0 +1,19 @@
+diff --git a/thirdparty/libwebp/src/dsp/cpu.h b/thirdparty/libwebp/src/dsp/cpu.h
+index c86540f280..4dbe607aec 100644
+--- a/thirdparty/libwebp/src/dsp/cpu.h
++++ b/thirdparty/libwebp/src/dsp/cpu.h
+@@ -47,12 +47,12 @@
+ // x86 defines.
+
+ #if !defined(HAVE_CONFIG_H)
+-#if defined(_MSC_VER) && _MSC_VER > 1310 && \
++#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER > 1310 && \
+ (defined(_M_X64) || defined(_M_IX86))
+ #define WEBP_MSC_SSE2 // Visual C++ SSE2 targets
+ #endif
+
+-#if defined(_MSC_VER) && _MSC_VER >= 1500 && \
++#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER >= 1500 && \
+ (defined(_M_X64) || defined(_M_IX86))
+ #define WEBP_MSC_SSE41 // Visual C++ SSE4.1 targets
+ #endif
diff --git a/thirdparty/libwebp/src/dsp/cpu.h b/thirdparty/libwebp/src/dsp/cpu.h
index c86540f280..4dbe607aec 100644
--- a/thirdparty/libwebp/src/dsp/cpu.h
+++ b/thirdparty/libwebp/src/dsp/cpu.h
@@ -47,12 +47,12 @@
// x86 defines.
#if !defined(HAVE_CONFIG_H)
-#if defined(_MSC_VER) && _MSC_VER > 1310 && \
+#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER > 1310 && \
(defined(_M_X64) || defined(_M_IX86))
#define WEBP_MSC_SSE2 // Visual C++ SSE2 targets
#endif
-#if defined(_MSC_VER) && _MSC_VER >= 1500 && \
+#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER >= 1500 && \
(defined(_M_X64) || defined(_M_IX86))
#define WEBP_MSC_SSE41 // Visual C++ SSE4.1 targets
#endif
diff --git a/thirdparty/mbedtls/include/mbedtls/bignum.h b/thirdparty/mbedtls/include/mbedtls/bignum.h
index 71d7b97672..8367cd34e6 100644
--- a/thirdparty/mbedtls/include/mbedtls/bignum.h
+++ b/thirdparty/mbedtls/include/mbedtls/bignum.h
@@ -880,7 +880,7 @@ int mbedtls_mpi_mod_int(mbedtls_mpi_uint *r, const mbedtls_mpi *A,
mbedtls_mpi_sint b);
/**
- * \brief Perform a sliding-window exponentiation: X = A^E mod N
+ * \brief Perform a modular exponentiation: X = A^E mod N
*
* \param X The destination MPI. This must point to an initialized MPI.
* This must not alias E or N.
diff --git a/thirdparty/mbedtls/include/mbedtls/build_info.h b/thirdparty/mbedtls/include/mbedtls/build_info.h
index eab167f383..8242ec6828 100644
--- a/thirdparty/mbedtls/include/mbedtls/build_info.h
+++ b/thirdparty/mbedtls/include/mbedtls/build_info.h
@@ -26,16 +26,16 @@
*/
#define MBEDTLS_VERSION_MAJOR 3
#define MBEDTLS_VERSION_MINOR 6
-#define MBEDTLS_VERSION_PATCH 0
+#define MBEDTLS_VERSION_PATCH 1
/**
* The single version number has the following structure:
* MMNNPP00
* Major version | Minor version | Patch version
*/
-#define MBEDTLS_VERSION_NUMBER 0x03060000
-#define MBEDTLS_VERSION_STRING "3.6.0"
-#define MBEDTLS_VERSION_STRING_FULL "Mbed TLS 3.6.0"
+#define MBEDTLS_VERSION_NUMBER 0x03060100
+#define MBEDTLS_VERSION_STRING "3.6.1"
+#define MBEDTLS_VERSION_STRING_FULL "Mbed TLS 3.6.1"
/* Macros for build-time platform detection */
@@ -101,6 +101,13 @@
#define inline __inline
#endif
+#if defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Something went wrong: MBEDTLS_CONFIG_FILES_READ defined before reading the config files!"
+#endif
+#if defined(MBEDTLS_CONFIG_IS_FINALIZED)
+#error "Something went wrong: MBEDTLS_CONFIG_IS_FINALIZED defined before reading the config files!"
+#endif
+
/* X.509, TLS and non-PSA crypto configuration */
#if !defined(MBEDTLS_CONFIG_FILE)
#include "mbedtls/mbedtls_config.h"
@@ -135,6 +142,12 @@
#endif
#endif /* defined(MBEDTLS_PSA_CRYPTO_CONFIG) */
+/* Indicate that all configuration files have been read.
+ * It is now time to adjust the configuration (follow through on dependencies,
+ * make PSA and legacy crypto consistent, etc.).
+ */
+#define MBEDTLS_CONFIG_FILES_READ
+
/* Auto-enable MBEDTLS_CTR_DRBG_USE_128_BIT_KEY if
* MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH and MBEDTLS_CTR_DRBG_C defined
* to ensure a 128-bit key size in CTR_DRBG.
@@ -169,8 +182,13 @@
#include "mbedtls/config_adjust_ssl.h"
-/* Make sure all configuration symbols are set before including check_config.h,
- * even the ones that are calculated programmatically. */
+/* Indicate that all configuration symbols are set,
+ * even the ones that are calculated programmatically.
+ * It is now safe to query the configuration (to check it, to size buffers,
+ * etc.).
+ */
+#define MBEDTLS_CONFIG_IS_FINALIZED
+
#include "mbedtls/check_config.h"
#endif /* MBEDTLS_BUILD_INFO_H */
diff --git a/thirdparty/mbedtls/include/mbedtls/check_config.h b/thirdparty/mbedtls/include/mbedtls/check_config.h
index b3c038dd2e..67a05f83b8 100644
--- a/thirdparty/mbedtls/include/mbedtls/check_config.h
+++ b/thirdparty/mbedtls/include/mbedtls/check_config.h
@@ -2,6 +2,13 @@
* \file check_config.h
*
* \brief Consistency checks for configuration options
+ *
+ * This is an internal header. Do not include it directly.
+ *
+ * This header is included automatically by all public Mbed TLS headers
+ * (via mbedtls/build_info.h). Do not include it directly in a configuration
+ * file such as mbedtls/mbedtls_config.h or #MBEDTLS_USER_CONFIG_FILE!
+ * It would run at the wrong time due to missing derived symbols.
*/
/*
* Copyright The Mbed TLS Contributors
@@ -12,6 +19,13 @@
#define MBEDTLS_CHECK_CONFIG_H
/* *INDENT-OFF* */
+
+#if !defined(MBEDTLS_CONFIG_IS_FINALIZED)
+#warning "Do not include mbedtls/check_config.h manually! " \
+ "This may cause spurious errors. " \
+ "It is included automatically at the right point since Mbed TLS 3.0."
+#endif /* !MBEDTLS_CONFIG_IS_FINALIZED */
+
/*
* We assume CHAR_BIT is 8 in many places. In practice, this is true on our
* target platforms, so not an issue, but let's just be extra sure.
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h
index 9b06041228..3ba987ebb2 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h
@@ -2,7 +2,9 @@
* \file mbedtls/config_adjust_legacy_crypto.h
* \brief Adjust legacy configuration configuration
*
- * Automatically enable certain dependencies. Generally, MBEDLTS_xxx
+ * This is an internal header. Do not include it directly.
+ *
+ * Automatically enable certain dependencies. Generally, MBEDTLS_xxx
* configurations need to be explicitly enabled by the user: enabling
* MBEDTLS_xxx_A but not MBEDTLS_xxx_B when A requires B results in a
* compilation error. However, we do automatically enable certain options
@@ -22,6 +24,14 @@
#ifndef MBEDTLS_CONFIG_ADJUST_LEGACY_CRYPTO_H
#define MBEDTLS_CONFIG_ADJUST_LEGACY_CRYPTO_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/* Ideally, we'd set those as defaults in mbedtls_config.h, but
* putting an #ifdef _WIN32 in mbedtls_config.h would confuse config.py.
*
@@ -48,7 +58,8 @@
defined(MBEDTLS_PSA_BUILTIN_ALG_ECB_NO_PADDING) || \
defined(MBEDTLS_PSA_BUILTIN_ALG_CBC_NO_PADDING) || \
defined(MBEDTLS_PSA_BUILTIN_ALG_CBC_PKCS7) || \
- defined(MBEDTLS_PSA_BUILTIN_ALG_CCM_STAR_NO_TAG))
+ defined(MBEDTLS_PSA_BUILTIN_ALG_CCM_STAR_NO_TAG) || \
+ defined(MBEDTLS_PSA_BUILTIN_ALG_CMAC))
#define MBEDTLS_CIPHER_C
#endif
@@ -293,6 +304,14 @@
#define MBEDTLS_ECP_LIGHT
#endif
+/* Backward compatibility: after #8740 the RSA module offers functions to parse
+ * and write RSA private/public keys without relying on the PK one. Of course
+ * this needs ASN1 support to do so, so we enable it here. */
+#if defined(MBEDTLS_RSA_C)
+#define MBEDTLS_ASN1_PARSE_C
+#define MBEDTLS_ASN1_WRITE_C
+#endif
+
/* MBEDTLS_PK_PARSE_EC_COMPRESSED is introduced in Mbed TLS version 3.5, while
* in previous version compressed points were automatically supported as long
* as PK_PARSE_C and ECP_C were enabled. As a consequence, for backward
@@ -409,12 +428,12 @@
/* psa_util file features some ECDSA conversion functions, to convert between
* legacy's ASN.1 DER format and PSA's raw one. */
-#if defined(MBEDTLS_ECDSA_C) || (defined(MBEDTLS_PSA_CRYPTO_C) && \
+#if (defined(MBEDTLS_PSA_CRYPTO_CLIENT) && \
(defined(PSA_WANT_ALG_ECDSA) || defined(PSA_WANT_ALG_DETERMINISTIC_ECDSA)))
#define MBEDTLS_PSA_UTIL_HAVE_ECDSA
#endif
-/* Some internal helpers to determine which keys are availble. */
+/* Some internal helpers to determine which keys are available. */
#if (!defined(MBEDTLS_USE_PSA_CRYPTO) && defined(MBEDTLS_AES_C)) || \
(defined(MBEDTLS_USE_PSA_CRYPTO) && defined(PSA_WANT_KEY_TYPE_AES))
#define MBEDTLS_SSL_HAVE_AES
@@ -428,7 +447,7 @@
#define MBEDTLS_SSL_HAVE_CAMELLIA
#endif
-/* Some internal helpers to determine which operation modes are availble. */
+/* Some internal helpers to determine which operation modes are available. */
#if (!defined(MBEDTLS_USE_PSA_CRYPTO) && defined(MBEDTLS_CIPHER_MODE_CBC)) || \
(defined(MBEDTLS_USE_PSA_CRYPTO) && defined(PSA_WANT_ALG_CBC_NO_PADDING))
#define MBEDTLS_SSL_HAVE_CBC
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h
index 0091e246b2..04bdae61bb 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h
@@ -2,6 +2,8 @@
* \file mbedtls/config_adjust_legacy_from_psa.h
* \brief Adjust PSA configuration: activate legacy implementations
*
+ * This is an internal header. Do not include it directly.
+ *
* When MBEDTLS_PSA_CRYPTO_CONFIG is enabled, activate legacy implementations
* of cryptographic mechanisms as needed to fulfill the needs of the PSA
* configuration. Generally speaking, we activate a legacy mechanism if
@@ -16,6 +18,14 @@
#ifndef MBEDTLS_CONFIG_ADJUST_LEGACY_FROM_PSA_H
#define MBEDTLS_CONFIG_ADJUST_LEGACY_FROM_PSA_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/* Define appropriate ACCEL macros for the p256-m driver.
* In the future, those should be generated from the drivers JSON description.
*/
@@ -498,7 +508,6 @@
* The PSA implementation has its own implementation of HKDF, separate from
* hkdf.c. No need to enable MBEDTLS_HKDF_C here.
*/
-#define MBEDTLS_PSA_BUILTIN_ALG_HMAC 1
#define MBEDTLS_PSA_BUILTIN_ALG_HKDF 1
#endif /* !MBEDTLS_PSA_ACCEL_ALG_HKDF */
#endif /* PSA_WANT_ALG_HKDF */
@@ -509,7 +518,6 @@
* The PSA implementation has its own implementation of HKDF, separate from
* hkdf.c. No need to enable MBEDTLS_HKDF_C here.
*/
-#define MBEDTLS_PSA_BUILTIN_ALG_HMAC 1
#define MBEDTLS_PSA_BUILTIN_ALG_HKDF_EXTRACT 1
#endif /* !MBEDTLS_PSA_ACCEL_ALG_HKDF_EXTRACT */
#endif /* PSA_WANT_ALG_HKDF_EXTRACT */
@@ -520,7 +528,6 @@
* The PSA implementation has its own implementation of HKDF, separate from
* hkdf.c. No need to enable MBEDTLS_HKDF_C here.
*/
-#define MBEDTLS_PSA_BUILTIN_ALG_HMAC 1
#define MBEDTLS_PSA_BUILTIN_ALG_HKDF_EXPAND 1
#endif /* !MBEDTLS_PSA_ACCEL_ALG_HKDF_EXPAND */
#endif /* PSA_WANT_ALG_HKDF_EXPAND */
@@ -630,9 +637,6 @@
#if !defined(MBEDTLS_PSA_ACCEL_ALG_PBKDF2_HMAC)
#define MBEDTLS_PSA_BUILTIN_ALG_PBKDF2_HMAC 1
#define PSA_HAVE_SOFT_PBKDF2_HMAC 1
-#if !defined(MBEDTLS_PSA_ACCEL_ALG_HMAC)
-#define MBEDTLS_PSA_BUILTIN_ALG_HMAC 1
-#endif /* !MBEDTLS_PSA_ACCEL_ALG_HMAC */
#endif /* !MBEDTLS_PSA_BUILTIN_ALG_PBKDF2_HMAC */
#endif /* PSA_WANT_ALG_PBKDF2_HMAC */
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h
index 3456615943..14ca14696f 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h
@@ -2,6 +2,8 @@
* \file mbedtls/config_adjust_psa_from_legacy.h
* \brief Adjust PSA configuration: construct PSA configuration from legacy
*
+ * This is an internal header. Do not include it directly.
+ *
* When MBEDTLS_PSA_CRYPTO_CONFIG is disabled, we automatically enable
* cryptographic mechanisms through the PSA interface when the corresponding
* legacy mechanism is enabled. In many cases, this just enables the PSA
@@ -18,6 +20,14 @@
#ifndef MBEDTLS_CONFIG_ADJUST_PSA_FROM_LEGACY_H
#define MBEDTLS_CONFIG_ADJUST_PSA_FROM_LEGACY_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/*
* Ensure PSA_WANT_* defines are setup properly if MBEDTLS_PSA_CRYPTO_CONFIG
* is not defined
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h
index 3a55c3f6e1..ef65cce0d9 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h
@@ -2,6 +2,8 @@
* \file mbedtls/config_adjust_psa_superset_legacy.h
* \brief Adjust PSA configuration: automatic enablement from legacy
*
+ * This is an internal header. Do not include it directly.
+ *
* To simplify some edge cases, we automatically enable certain cryptographic
* mechanisms in the PSA API if they are enabled in the legacy API. The general
* idea is that if legacy module M uses mechanism A internally, and A has
@@ -17,6 +19,14 @@
#ifndef MBEDTLS_CONFIG_ADJUST_PSA_SUPERSET_LEGACY_H
#define MBEDTLS_CONFIG_ADJUST_PSA_SUPERSET_LEGACY_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/****************************************************************/
/* Hashes that are built in are also enabled in PSA.
* This simplifies dependency declarations especially
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h
index 39c7b3b117..1f82d9c006 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h
@@ -2,7 +2,9 @@
* \file mbedtls/config_adjust_ssl.h
* \brief Adjust TLS configuration
*
- * Automatically enable certain dependencies. Generally, MBEDLTS_xxx
+ * This is an internal header. Do not include it directly.
+ *
+ * Automatically enable certain dependencies. Generally, MBEDTLS_xxx
* configurations need to be explicitly enabled by the user: enabling
* MBEDTLS_xxx_A but not MBEDTLS_xxx_B when A requires B results in a
* compilation error. However, we do automatically enable certain options
@@ -22,6 +24,14 @@
#ifndef MBEDTLS_CONFIG_ADJUST_SSL_H
#define MBEDTLS_CONFIG_ADJUST_SSL_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/* The following blocks make it easier to disable all of TLS,
* or of TLS 1.2 or 1.3 or DTLS, without having to manually disable all
* key exchanges, options and extensions related to them. */
diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h
index 346c8ae6d5..cfb2d88916 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h
@@ -2,7 +2,9 @@
* \file mbedtls/config_adjust_x509.h
* \brief Adjust X.509 configuration
*
- * Automatically enable certain dependencies. Generally, MBEDLTS_xxx
+ * This is an internal header. Do not include it directly.
+ *
+ * Automatically enable certain dependencies. Generally, MBEDTLS_xxx
* configurations need to be explicitly enabled by the user: enabling
* MBEDTLS_xxx_A but not MBEDTLS_xxx_B when A requires B results in a
* compilation error. However, we do automatically enable certain options
@@ -22,4 +24,12 @@
#ifndef MBEDTLS_CONFIG_ADJUST_X509_H
#define MBEDTLS_CONFIG_ADJUST_X509_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
#endif /* MBEDTLS_CONFIG_ADJUST_X509_H */
diff --git a/thirdparty/mbedtls/include/mbedtls/config_psa.h b/thirdparty/mbedtls/include/mbedtls/config_psa.h
index 17da61b3e8..5f3d0f3d5d 100644
--- a/thirdparty/mbedtls/include/mbedtls/config_psa.h
+++ b/thirdparty/mbedtls/include/mbedtls/config_psa.h
@@ -22,6 +22,8 @@
#include "psa/crypto_adjust_config_synonyms.h"
+#include "psa/crypto_adjust_config_dependencies.h"
+
#include "mbedtls/config_adjust_psa_superset_legacy.h"
#if defined(MBEDTLS_PSA_CRYPTO_CONFIG)
@@ -32,7 +34,11 @@
* before we deduce what built-ins are required. */
#include "psa/crypto_adjust_config_key_pair_types.h"
+#if defined(MBEDTLS_PSA_CRYPTO_C)
+/* If we are implementing PSA crypto ourselves, then we want to enable the
+ * required built-ins. Otherwise, PSA features will be provided by the server. */
#include "mbedtls/config_adjust_legacy_from_psa.h"
+#endif
#else /* MBEDTLS_PSA_CRYPTO_CONFIG */
diff --git a/thirdparty/mbedtls/include/mbedtls/ctr_drbg.h b/thirdparty/mbedtls/include/mbedtls/ctr_drbg.h
index c00756df1b..0b7cce1923 100644
--- a/thirdparty/mbedtls/include/mbedtls/ctr_drbg.h
+++ b/thirdparty/mbedtls/include/mbedtls/ctr_drbg.h
@@ -32,12 +32,27 @@
#include "mbedtls/build_info.h"
-/* In case AES_C is defined then it is the primary option for backward
- * compatibility purposes. If that's not available, PSA is used instead */
-#if defined(MBEDTLS_AES_C)
-#include "mbedtls/aes.h"
-#else
+/* The CTR_DRBG implementation can either directly call the low-level AES
+ * module (gated by MBEDTLS_AES_C) or call the PSA API to perform AES
+ * operations. Calling the AES module directly is the default, both for
+ * maximum backward compatibility and because it's a bit more efficient
+ * (less glue code).
+ *
+ * When MBEDTLS_AES_C is disabled, the CTR_DRBG module calls PSA crypto and
+ * thus benefits from the PSA AES accelerator driver.
+ * It is technically possible to enable MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO
+ * to use PSA even when MBEDTLS_AES_C is enabled, but there is very little
+ * reason to do so other than testing purposes and this is not officially
+ * supported.
+ */
+#if !defined(MBEDTLS_AES_C)
+#define MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO
+#endif
+
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
#include "psa/crypto.h"
+#else
+#include "mbedtls/aes.h"
#endif
#include "entropy.h"
@@ -157,7 +172,7 @@ extern "C" {
#define MBEDTLS_CTR_DRBG_ENTROPY_NONCE_LEN (MBEDTLS_CTR_DRBG_ENTROPY_LEN + 1) / 2
#endif
-#if !defined(MBEDTLS_AES_C)
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
typedef struct mbedtls_ctr_drbg_psa_context {
mbedtls_svc_key_id_t key_id;
psa_cipher_operation_t operation;
@@ -189,10 +204,10 @@ typedef struct mbedtls_ctr_drbg_context {
* This is the maximum number of requests
* that can be made between reseedings. */
-#if defined(MBEDTLS_AES_C)
- mbedtls_aes_context MBEDTLS_PRIVATE(aes_ctx); /*!< The AES context. */
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
mbedtls_ctr_drbg_psa_context MBEDTLS_PRIVATE(psa_ctx); /*!< The PSA context. */
+#else
+ mbedtls_aes_context MBEDTLS_PRIVATE(aes_ctx); /*!< The AES context. */
#endif
/*
diff --git a/thirdparty/mbedtls/include/mbedtls/ecdh.h b/thirdparty/mbedtls/include/mbedtls/ecdh.h
index a0909d6b44..a6a5069337 100644
--- a/thirdparty/mbedtls/include/mbedtls/ecdh.h
+++ b/thirdparty/mbedtls/include/mbedtls/ecdh.h
@@ -325,7 +325,7 @@ int mbedtls_ecdh_read_params(mbedtls_ecdh_context *ctx,
* \brief This function sets up an ECDH context from an EC key.
*
* It is used by clients and servers in place of the
- * ServerKeyEchange for static ECDH, and imports ECDH
+ * ServerKeyExchange for static ECDH, and imports ECDH
* parameters from the EC key information of a certificate.
*
* \see ecp.h
diff --git a/thirdparty/mbedtls/include/mbedtls/ecp.h b/thirdparty/mbedtls/include/mbedtls/ecp.h
index d8f73ae965..623910bcbd 100644
--- a/thirdparty/mbedtls/include/mbedtls/ecp.h
+++ b/thirdparty/mbedtls/include/mbedtls/ecp.h
@@ -216,7 +216,7 @@ mbedtls_ecp_point;
* range of <code>0..2^(2*pbits)-1</code>, and transforms it in-place to an integer
* which is congruent mod \p P to the given MPI, and is close enough to \p pbits
* in size, so that it may be efficiently brought in the 0..P-1 range by a few
- * additions or subtractions. Therefore, it is only an approximative modular
+ * additions or subtractions. Therefore, it is only an approximate modular
* reduction. It must return 0 on success and non-zero on failure.
*
* \note Alternative implementations of the ECP module must obey the
diff --git a/thirdparty/mbedtls/include/mbedtls/mbedtls_config.h b/thirdparty/mbedtls/include/mbedtls/mbedtls_config.h
index 35921412c6..bd3f71d5bc 100644
--- a/thirdparty/mbedtls/include/mbedtls/mbedtls_config.h
+++ b/thirdparty/mbedtls/include/mbedtls/mbedtls_config.h
@@ -1118,7 +1118,7 @@
* MBEDTLS_ECP_DP_SECP256R1_ENABLED
*
* \warning If SHA-256 is provided only by a PSA driver, you must call
- * psa_crypto_init() before the first hanshake (even if
+ * psa_crypto_init() before the first handshake (even if
* MBEDTLS_USE_PSA_CRYPTO is disabled).
*
* This enables the following ciphersuites (if other requisites are
@@ -1415,6 +1415,23 @@
//#define MBEDTLS_PSA_CRYPTO_SPM
/**
+ * \def MBEDTLS_PSA_KEY_STORE_DYNAMIC
+ *
+ * Dynamically resize the PSA key store to accommodate any number of
+ * volatile keys (until the heap memory is exhausted).
+ *
+ * If this option is disabled, the key store has a fixed size
+ * #MBEDTLS_PSA_KEY_SLOT_COUNT for volatile keys and loaded persistent keys
+ * together.
+ *
+ * This option has no effect when #MBEDTLS_PSA_CRYPTO_C is disabled.
+ *
+ * Module: library/psa_crypto.c
+ * Requires: MBEDTLS_PSA_CRYPTO_C
+ */
+#define MBEDTLS_PSA_KEY_STORE_DYNAMIC
+
+/**
* Uncomment to enable p256-m. This is an alternative implementation of
* key generation, ECDH and (randomized) ECDSA on the curve SECP256R1.
* Compared to the default implementation:
@@ -1781,8 +1798,9 @@
* Requires: MBEDTLS_PSA_CRYPTO_C
*
* \note TLS 1.3 uses PSA crypto for cryptographic operations that are
- * directly performed by TLS 1.3 code. As a consequence, you must
- * call psa_crypto_init() before the first TLS 1.3 handshake.
+ * directly performed by TLS 1.3 code. As a consequence, when TLS 1.3
+ * is enabled, a TLS handshake may call psa_crypto_init(), even
+ * if it ends up negotiating a different TLS version.
*
* \note Cryptographic operations performed indirectly via another module
* (X.509, PK) or by code shared with TLS 1.2 (record protection,
@@ -2625,7 +2643,7 @@
* The CTR_DRBG generator uses AES-256 by default.
* To use AES-128 instead, enable \c MBEDTLS_CTR_DRBG_USE_128_BIT_KEY above.
*
- * AES support can either be achived through builtin (MBEDTLS_AES_C) or PSA.
+ * AES support can either be achieved through builtin (MBEDTLS_AES_C) or PSA.
* Builtin is the default option when MBEDTLS_AES_C is defined otherwise PSA
* is used.
*
@@ -4016,22 +4034,38 @@
* Use HMAC_DRBG with the specified hash algorithm for HMAC_DRBG for the
* PSA crypto subsystem.
*
- * If this option is unset:
- * - If CTR_DRBG is available, the PSA subsystem uses it rather than HMAC_DRBG.
- * - Otherwise, the PSA subsystem uses HMAC_DRBG with either
- * #MBEDTLS_MD_SHA512 or #MBEDTLS_MD_SHA256 based on availability and
- * on unspecified heuristics.
+ * If this option is unset, the library chooses a hash (currently between
+ * #MBEDTLS_MD_SHA512 and #MBEDTLS_MD_SHA256) based on availability and
+ * unspecified heuristics.
+ *
+ * \note The PSA crypto subsystem uses the first available mechanism amongst
+ * the following:
+ * - #MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG if enabled;
+ * - Entropy from #MBEDTLS_ENTROPY_C plus CTR_DRBG with AES
+ * if #MBEDTLS_CTR_DRBG_C is enabled;
+ * - Entropy from #MBEDTLS_ENTROPY_C plus HMAC_DRBG.
+ *
+ * A future version may reevaluate the prioritization of DRBG mechanisms.
*/
//#define MBEDTLS_PSA_HMAC_DRBG_MD_TYPE MBEDTLS_MD_SHA256
/** \def MBEDTLS_PSA_KEY_SLOT_COUNT
- * Restrict the PSA library to supporting a maximum amount of simultaneously
- * loaded keys. A loaded key is a key stored by the PSA Crypto core as a
- * volatile key, or a persistent key which is loaded temporarily by the
- * library as part of a crypto operation in flight.
*
- * If this option is unset, the library will fall back to a default value of
- * 32 keys.
+ * When #MBEDTLS_PSA_KEY_STORE_DYNAMIC is disabled,
+ * the maximum amount of PSA keys simultaneously in memory. This counts all
+ * volatile keys, plus loaded persistent keys.
+ *
+ * When #MBEDTLS_PSA_KEY_STORE_DYNAMIC is enabled,
+ * the maximum number of loaded persistent keys.
+ *
+ * Currently, persistent keys do not need to be loaded all the time while
+ * a multipart operation is in progress, only while the operation is being
+ * set up. This may change in future versions of the library.
+ *
+ * Currently, the library traverses of the whole table on each access to a
+ * persistent key. Therefore large values may cause poor performance.
+ *
+ * This option has no effect when #MBEDTLS_PSA_CRYPTO_C is disabled.
*/
//#define MBEDTLS_PSA_KEY_SLOT_COUNT 32
diff --git a/thirdparty/mbedtls/include/mbedtls/pk.h b/thirdparty/mbedtls/include/mbedtls/pk.h
index fde302f872..52f4cc6c9e 100644
--- a/thirdparty/mbedtls/include/mbedtls/pk.h
+++ b/thirdparty/mbedtls/include/mbedtls/pk.h
@@ -359,32 +359,40 @@ int mbedtls_pk_setup(mbedtls_pk_context *ctx, const mbedtls_pk_info_t *info);
#if defined(MBEDTLS_USE_PSA_CRYPTO)
/**
- * \brief Initialize a PK context to wrap a PSA key.
- *
- * \note This function replaces mbedtls_pk_setup() for contexts
- * that wrap a (possibly opaque) PSA key instead of
- * storing and manipulating the key material directly.
- *
- * \param ctx The context to initialize. It must be empty (type NONE).
- * \param key The PSA key to wrap, which must hold an ECC or RSA key
- * pair (see notes below).
- *
- * \note The wrapped key must remain valid as long as the
- * wrapping PK context is in use, that is at least between
- * the point this function is called and the point
- * mbedtls_pk_free() is called on this context. The wrapped
- * key might then be independently used or destroyed.
- *
- * \note This function is currently only available for ECC or RSA
- * key pairs (that is, keys containing private key material).
- * Support for other key types may be added later.
- *
- * \return \c 0 on success.
- * \return #MBEDTLS_ERR_PK_BAD_INPUT_DATA on invalid input
- * (context already used, invalid key identifier).
- * \return #MBEDTLS_ERR_PK_FEATURE_UNAVAILABLE if the key is not an
- * ECC key pair.
- * \return #MBEDTLS_ERR_PK_ALLOC_FAILED on allocation failure.
+ * \brief Initialize a PK context to wrap a PSA key.
+ *
+ * This function creates a PK context which wraps a PSA key. The PSA wrapped
+ * key must be an EC or RSA key pair (DH is not suported in the PK module).
+ *
+ * Under the hood PSA functions will be used to perform the required
+ * operations and, based on the key type, used algorithms will be:
+ * * EC:
+ * * verify, verify_ext, sign, sign_ext: ECDSA.
+ * * RSA:
+ * * sign, decrypt: use the primary algorithm in the wrapped PSA key;
+ * * sign_ext: RSA PSS if the pk_type is #MBEDTLS_PK_RSASSA_PSS, otherwise
+ * it falls back to the sign() case;
+ * * verify, verify_ext, encrypt: not supported.
+ *
+ * In order for the above operations to succeed, the policy of the wrapped PSA
+ * key must allow the specified algorithm.
+ *
+ * Opaque PK contexts wrapping an EC keys also support \c mbedtls_pk_check_pair(),
+ * whereas RSA ones do not.
+ *
+ * \warning The PSA wrapped key must remain valid as long as the wrapping PK
+ * context is in use, that is at least between the point this function
+ * is called and the point mbedtls_pk_free() is called on this context.
+ *
+ * \param ctx The context to initialize. It must be empty (type NONE).
+ * \param key The PSA key to wrap, which must hold an ECC or RSA key pair.
+ *
+ * \return \c 0 on success.
+ * \return #MBEDTLS_ERR_PK_BAD_INPUT_DATA on invalid input (context already
+ * used, invalid key identifier).
+ * \return #MBEDTLS_ERR_PK_FEATURE_UNAVAILABLE if the key is not an ECC or
+ * RSA key pair.
+ * \return #MBEDTLS_ERR_PK_ALLOC_FAILED on allocation failure.
*/
int mbedtls_pk_setup_opaque(mbedtls_pk_context *ctx,
const mbedtls_svc_key_id_t key);
diff --git a/thirdparty/mbedtls/include/mbedtls/ssl.h b/thirdparty/mbedtls/include/mbedtls/ssl.h
index 172d4693b2..42fffbf860 100644
--- a/thirdparty/mbedtls/include/mbedtls/ssl.h
+++ b/thirdparty/mbedtls/include/mbedtls/ssl.h
@@ -83,10 +83,7 @@
/** Processing of the Certificate handshake message failed. */
#define MBEDTLS_ERR_SSL_BAD_CERTIFICATE -0x7A00
/* Error space gap */
-/**
- * Received NewSessionTicket Post Handshake Message.
- * This error code is experimental and may be changed or removed without notice.
- */
+/** A TLS 1.3 NewSessionTicket message has been received. */
#define MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET -0x7B00
/** Not possible to read early data */
#define MBEDTLS_ERR_SSL_CANNOT_READ_EARLY_DATA -0x7B80
@@ -324,6 +321,9 @@
#define MBEDTLS_SSL_SESSION_TICKETS_DISABLED 0
#define MBEDTLS_SSL_SESSION_TICKETS_ENABLED 1
+#define MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_DISABLED 0
+#define MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED 1
+
#define MBEDTLS_SSL_PRESET_DEFAULT 0
#define MBEDTLS_SSL_PRESET_SUITEB 2
@@ -1446,6 +1446,12 @@ struct mbedtls_ssl_config {
#endif
#if defined(MBEDTLS_SSL_SESSION_TICKETS) && \
defined(MBEDTLS_SSL_CLI_C)
+ /** Encodes two booleans, one stating whether TLS 1.2 session tickets are
+ * enabled or not, the other one whether the handling of TLS 1.3
+ * NewSessionTicket messages is enabled or not. They are respectively set
+ * by mbedtls_ssl_conf_session_tickets() and
+ * mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets().
+ */
uint8_t MBEDTLS_PRIVATE(session_tickets); /*!< use session tickets? */
#endif
@@ -2364,7 +2370,7 @@ int mbedtls_ssl_set_cid(mbedtls_ssl_context *ssl,
*/
int mbedtls_ssl_get_own_cid(mbedtls_ssl_context *ssl,
int *enabled,
- unsigned char own_cid[MBEDTLS_SSL_CID_OUT_LEN_MAX],
+ unsigned char own_cid[MBEDTLS_SSL_CID_IN_LEN_MAX],
size_t *own_cid_len);
/**
@@ -3216,16 +3222,16 @@ void mbedtls_ssl_conf_session_cache(mbedtls_ssl_config *conf,
* a full handshake.
*
* \note This function can handle a variety of mechanisms for session
- * resumption: For TLS 1.2, both session ID-based resumption and
- * ticket-based resumption will be considered. For TLS 1.3,
- * once implemented, sessions equate to tickets, and loading
- * one or more sessions via this call will lead to their
- * corresponding tickets being advertised as resumption PSKs
- * by the client.
- *
- * \note Calling this function multiple times will only be useful
- * once TLS 1.3 is supported. For TLS 1.2 connections, this
- * function should be called at most once.
+ * resumption: For TLS 1.2, both session ID-based resumption
+ * and ticket-based resumption will be considered. For TLS 1.3,
+ * sessions equate to tickets, and loading one session by
+ * calling this function will lead to its corresponding ticket
+ * being advertised as resumption PSK by the client. This
+ * depends on session tickets being enabled (see
+ * #MBEDTLS_SSL_SESSION_TICKETS configuration option) though.
+ * If session tickets are disabled, a call to this function
+ * with a TLS 1.3 session, will not have any effect on the next
+ * handshake for the SSL context \p ssl.
*
* \param ssl The SSL context representing the connection which should
* be attempted to be setup using session resumption. This
@@ -3240,9 +3246,10 @@ void mbedtls_ssl_conf_session_cache(mbedtls_ssl_config *conf,
*
* \return \c 0 if successful.
* \return \c MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE if the session
- * could not be loaded because of an implementation limitation.
- * This error is non-fatal, and has no observable effect on
- * the SSL context or the session that was attempted to be loaded.
+ * could not be loaded because one session has already been
+ * loaded. This error is non-fatal, and has no observable
+ * effect on the SSL context or the session that was attempted
+ * to be loaded.
* \return Another negative error code on other kinds of failure.
*
* \sa mbedtls_ssl_get_session()
@@ -3309,8 +3316,16 @@ int mbedtls_ssl_session_load(mbedtls_ssl_session *session,
* to determine the necessary size by calling this function
* with \p buf set to \c NULL and \p buf_len to \c 0.
*
+ * \note For TLS 1.3 sessions, this feature is supported only if the
+ * MBEDTLS_SSL_SESSION_TICKETS configuration option is enabled,
+ * as in TLS 1.3 session resumption is possible only with
+ * tickets.
+ *
* \return \c 0 if successful.
* \return #MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL if \p buf is too small.
+ * \return #MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE if the
+ * MBEDTLS_SSL_SESSION_TICKETS configuration option is disabled
+ * and the session is a TLS 1.3 session.
*/
int mbedtls_ssl_session_save(const mbedtls_ssl_session *session,
unsigned char *buf,
@@ -4456,21 +4471,50 @@ int mbedtls_ssl_conf_max_frag_len(mbedtls_ssl_config *conf, unsigned char mfl_co
void mbedtls_ssl_conf_preference_order(mbedtls_ssl_config *conf, int order);
#endif /* MBEDTLS_SSL_SRV_C */
-#if defined(MBEDTLS_SSL_SESSION_TICKETS) && \
- defined(MBEDTLS_SSL_CLI_C)
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
/**
- * \brief Enable / Disable session tickets (client only).
- * (Default: MBEDTLS_SSL_SESSION_TICKETS_ENABLED.)
+ * \brief Enable / Disable TLS 1.2 session tickets (client only,
+ * TLS 1.2 only). Enabled by default.
*
* \note On server, use \c mbedtls_ssl_conf_session_tickets_cb().
*
* \param conf SSL configuration
- * \param use_tickets Enable or disable (MBEDTLS_SSL_SESSION_TICKETS_ENABLED or
- * MBEDTLS_SSL_SESSION_TICKETS_DISABLED)
+ * \param use_tickets Enable or disable (#MBEDTLS_SSL_SESSION_TICKETS_ENABLED or
+ * #MBEDTLS_SSL_SESSION_TICKETS_DISABLED)
*/
void mbedtls_ssl_conf_session_tickets(mbedtls_ssl_config *conf, int use_tickets);
-#endif /* MBEDTLS_SSL_SESSION_TICKETS &&
- MBEDTLS_SSL_CLI_C */
+
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+/**
+ * \brief Enable / Disable handling of TLS 1.3 NewSessionTicket messages
+ * (client only, TLS 1.3 only).
+ *
+ * The handling of TLS 1.3 NewSessionTicket messages is disabled by
+ * default.
+ *
+ * In TLS 1.3, servers may send a NewSessionTicket message at any time,
+ * and may send multiple NewSessionTicket messages. By default, TLS 1.3
+ * clients ignore NewSessionTicket messages.
+ *
+ * To support session tickets in TLS 1.3 clients, call this function
+ * with #MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED. When
+ * this is enabled, when a client receives a NewSessionTicket message,
+ * the next call to a message processing functions (notably
+ * mbedtls_ssl_handshake() and mbedtls_ssl_read()) will return
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET. The client should then
+ * call mbedtls_ssl_get_session() to retrieve the session ticket before
+ * calling the same message processing function again.
+ *
+ * \param conf SSL configuration
+ * \param signal_new_session_tickets Enable or disable
+ * (#MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED or
+ * #MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_DISABLED)
+ */
+void mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets(
+ mbedtls_ssl_config *conf, int signal_new_session_tickets);
+
+#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
+#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */
#if defined(MBEDTLS_SSL_SESSION_TICKETS) && \
defined(MBEDTLS_SSL_SRV_C) && \
@@ -4837,23 +4881,16 @@ const mbedtls_x509_crt *mbedtls_ssl_get_peer_cert(const mbedtls_ssl_context *ssl
* \note This function can handle a variety of mechanisms for session
* resumption: For TLS 1.2, both session ID-based resumption and
* ticket-based resumption will be considered. For TLS 1.3,
- * once implemented, sessions equate to tickets, and calling
- * this function multiple times will export the available
- * tickets one a time until no further tickets are available,
- * in which case MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE will
- * be returned.
- *
- * \note Calling this function multiple times will only be useful
- * once TLS 1.3 is supported. For TLS 1.2 connections, this
- * function should be called at most once.
+ * sessions equate to tickets, and if session tickets are
+ * enabled (see #MBEDTLS_SSL_SESSION_TICKETS configuration
+ * option), this function exports the last received ticket and
+ * the exported session may be used to resume the TLS 1.3
+ * session. If session tickets are disabled, exported sessions
+ * cannot be used to resume a TLS 1.3 session.
*
* \return \c 0 if successful. In this case, \p session can be used for
* session resumption by passing it to mbedtls_ssl_set_session(),
* and serialized for storage via mbedtls_ssl_session_save().
- * \return #MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE if no further session
- * is available for export.
- * This error is a non-fatal, and has no observable effect on
- * the SSL context or the destination session.
* \return Another negative error code on other kinds of failure.
*
* \sa mbedtls_ssl_set_session()
@@ -4885,6 +4922,10 @@ int mbedtls_ssl_get_session(const mbedtls_ssl_context *ssl,
* \return #MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED if DTLS is in use
* and the client did not demonstrate reachability yet - in
* this case you must stop using the context (see below).
+ * \return #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET if a TLS 1.3
+ * NewSessionTicket message has been received. See the
+ * documentation of mbedtls_ssl_read() for more information
+ * about this error code.
* \return #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA if early data, as
* defined in RFC 8446 (TLS 1.3 specification), has been
* received as part of the handshake. This is server specific
@@ -4901,6 +4942,7 @@ int mbedtls_ssl_get_session(const mbedtls_ssl_context *ssl,
* #MBEDTLS_ERR_SSL_WANT_WRITE,
* #MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS or
* #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS or
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET or
* #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA,
* you must stop using the SSL context for reading or writing,
* and either free it or call \c mbedtls_ssl_session_reset()
@@ -4921,10 +4963,13 @@ int mbedtls_ssl_get_session(const mbedtls_ssl_context *ssl,
* currently being processed might or might not contain further
* DTLS records.
*
- * \note If the context is configured to allow TLS 1.3, or if
- * #MBEDTLS_USE_PSA_CRYPTO is enabled, the PSA crypto
+ * \note If #MBEDTLS_USE_PSA_CRYPTO is enabled, the PSA crypto
* subsystem must have been initialized by calling
* psa_crypto_init() before calling this function.
+ * Otherwise, the handshake may call psa_crypto_init()
+ * if a negotiation involving TLS 1.3 takes place (this may
+ * be the case even if TLS 1.3 is offered but eventually
+ * not selected).
*/
int mbedtls_ssl_handshake(mbedtls_ssl_context *ssl);
@@ -4972,6 +5017,7 @@ static inline int mbedtls_ssl_is_handshake_over(mbedtls_ssl_context *ssl)
* #MBEDTLS_ERR_SSL_WANT_READ, #MBEDTLS_ERR_SSL_WANT_WRITE,
* #MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS,
* #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS or
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET or
* #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA, you must stop using
* the SSL context for reading or writing, and either free it
* or call \c mbedtls_ssl_session_reset() on it before
@@ -5040,6 +5086,17 @@ int mbedtls_ssl_renegotiate(mbedtls_ssl_context *ssl);
* \return #MBEDTLS_ERR_SSL_CLIENT_RECONNECT if we're at the server
* side of a DTLS connection and the client is initiating a
* new connection using the same source port. See below.
+ * \return #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET if a TLS 1.3
+ * NewSessionTicket message has been received.
+ * This error code is only returned on the client side. It is
+ * only returned if handling of TLS 1.3 NewSessionTicket
+ * messages has been enabled through
+ * mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets().
+ * This error code indicates that a TLS 1.3 NewSessionTicket
+ * message has been received and parsed successfully by the
+ * client. The ticket data can be retrieved from the SSL
+ * context by calling mbedtls_ssl_get_session(). It remains
+ * available until the next call to mbedtls_ssl_read().
* \return #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA if early data, as
* defined in RFC 8446 (TLS 1.3 specification), has been
* received as part of the handshake. This is server specific
@@ -5057,6 +5114,7 @@ int mbedtls_ssl_renegotiate(mbedtls_ssl_context *ssl);
* #MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS,
* #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS,
* #MBEDTLS_ERR_SSL_CLIENT_RECONNECT or
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET or
* #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA,
* you must stop using the SSL context for reading or writing,
* and either free it or call \c mbedtls_ssl_session_reset()
@@ -5122,6 +5180,10 @@ int mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len);
* operation is in progress (see mbedtls_ecp_set_max_ops()) -
* in this case you must call this function again to complete
* the handshake when you're done attending other tasks.
+ * \return #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET if a TLS 1.3
+ * NewSessionTicket message has been received. See the
+ * documentation of mbedtls_ssl_read() for more information
+ * about this error code.
* \return #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA if early data, as
* defined in RFC 8446 (TLS 1.3 specification), has been
* received as part of the handshake. This is server specific
@@ -5138,6 +5200,7 @@ int mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len);
* #MBEDTLS_ERR_SSL_WANT_WRITE,
* #MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS,
* #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS or
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET or
* #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA,
* you must stop using the SSL context for reading or writing,
* and either free it or call \c mbedtls_ssl_session_reset()
diff --git a/thirdparty/mbedtls/include/psa/crypto.h b/thirdparty/mbedtls/include/psa/crypto.h
index 390534edf5..96baf8f3ed 100644
--- a/thirdparty/mbedtls/include/psa/crypto.h
+++ b/thirdparty/mbedtls/include/psa/crypto.h
@@ -121,8 +121,8 @@ static psa_key_attributes_t psa_key_attributes_init(void);
* value in the structure.
* The persistent key will be written to storage when the attribute
* structure is passed to a key creation function such as
- * psa_import_key(), psa_generate_key(), psa_generate_key_ext(),
- * psa_key_derivation_output_key(), psa_key_derivation_output_key_ext()
+ * psa_import_key(), psa_generate_key(), psa_generate_key_custom(),
+ * psa_key_derivation_output_key(), psa_key_derivation_output_key_custom()
* or psa_copy_key().
*
* This function may be declared as `static` (i.e. without external
@@ -131,6 +131,9 @@ static psa_key_attributes_t psa_key_attributes_init(void);
*
* \param[out] attributes The attribute structure to write to.
* \param key The persistent identifier for the key.
+ * This can be any value in the range from
+ * #PSA_KEY_ID_USER_MIN to #PSA_KEY_ID_USER_MAX
+ * inclusive.
*/
static void psa_set_key_id(psa_key_attributes_t *attributes,
mbedtls_svc_key_id_t key);
@@ -166,8 +169,8 @@ static void mbedtls_set_key_owner_id(psa_key_attributes_t *attributes,
* value in the structure.
* The persistent key will be written to storage when the attribute
* structure is passed to a key creation function such as
- * psa_import_key(), psa_generate_key(), psa_generate_key_ext(),
- * psa_key_derivation_output_key(), psa_key_derivation_output_key_ext()
+ * psa_import_key(), psa_generate_key(), psa_generate_key_custom(),
+ * psa_key_derivation_output_key(), psa_key_derivation_output_key_custom()
* or psa_copy_key().
*
* This function may be declared as `static` (i.e. without external
@@ -875,7 +878,7 @@ psa_status_t psa_hash_compute(psa_algorithm_t alg,
* such that #PSA_ALG_IS_HASH(\p alg) is true).
* \param[in] input Buffer containing the message to hash.
* \param input_length Size of the \p input buffer in bytes.
- * \param[out] hash Buffer containing the expected hash value.
+ * \param[in] hash Buffer containing the expected hash value.
* \param hash_length Size of the \p hash buffer in bytes.
*
* \retval #PSA_SUCCESS
@@ -1230,7 +1233,7 @@ psa_status_t psa_mac_compute(mbedtls_svc_key_id_t key,
* such that #PSA_ALG_IS_MAC(\p alg) is true).
* \param[in] input Buffer containing the input message.
* \param input_length Size of the \p input buffer in bytes.
- * \param[out] mac Buffer containing the expected MAC value.
+ * \param[in] mac Buffer containing the expected MAC value.
* \param mac_length Size of the \p mac buffer in bytes.
*
* \retval #PSA_SUCCESS
@@ -2922,7 +2925,7 @@ psa_status_t psa_sign_message(mbedtls_svc_key_id_t key,
* \p key.
* \param[in] input The message whose signature is to be verified.
* \param[in] input_length Size of the \p input buffer in bytes.
- * \param[out] signature Buffer containing the signature to verify.
+ * \param[in] signature Buffer containing the signature to verify.
* \param[in] signature_length Size of the \p signature buffer in bytes.
*
* \retval #PSA_SUCCESS \emptydescription
@@ -3248,7 +3251,7 @@ static psa_key_derivation_operation_t psa_key_derivation_operation_init(void);
* of or after providing inputs. For some algorithms, this step is mandatory
* because the output depends on the maximum capacity.
* -# To derive a key, call psa_key_derivation_output_key() or
- * psa_key_derivation_output_key_ext().
+ * psa_key_derivation_output_key_custom().
* To derive a byte string for a different purpose, call
* psa_key_derivation_output_bytes().
* Successive calls to these functions use successive output bytes
@@ -3471,7 +3474,7 @@ psa_status_t psa_key_derivation_input_integer(
* \note Once all inputs steps are completed, the operations will allow:
* - psa_key_derivation_output_bytes() if each input was either a direct input
* or a key with #PSA_KEY_USAGE_DERIVE set;
- * - psa_key_derivation_output_key() or psa_key_derivation_output_key_ext()
+ * - psa_key_derivation_output_key() or psa_key_derivation_output_key_custom()
* if the input for step
* #PSA_KEY_DERIVATION_INPUT_SECRET or #PSA_KEY_DERIVATION_INPUT_PASSWORD
* was from a key slot with #PSA_KEY_USAGE_DERIVE and each other input was
@@ -3721,9 +3724,9 @@ psa_status_t psa_key_derivation_output_bytes(
* on the derived key based on the attributes and strength of the secret key.
*
* \note This function is equivalent to calling
- * psa_key_derivation_output_key_ext()
- * with the production parameters #PSA_KEY_PRODUCTION_PARAMETERS_INIT
- * and `params_data_length == 0` (i.e. `params->data` is empty).
+ * psa_key_derivation_output_key_custom()
+ * with the custom production parameters #PSA_CUSTOM_KEY_PARAMETERS_INIT
+ * and `custom_data_length == 0` (i.e. `custom_data` is empty).
*
* \param[in] attributes The attributes for the new key.
* If the key type to be created is
@@ -3795,6 +3798,85 @@ psa_status_t psa_key_derivation_output_key(
* the policy must be the same as in the current
* operation.
* \param[in,out] operation The key derivation operation object to read from.
+ * \param[in] custom Customization parameters for the key generation.
+ * When this is #PSA_CUSTOM_KEY_PARAMETERS_INIT
+ * with \p custom_data_length = 0,
+ * this function is equivalent to
+ * psa_key_derivation_output_key().
+ * \param[in] custom_data Variable-length data associated with \c custom.
+ * \param custom_data_length
+ * Length of `custom_data` in bytes.
+ * \param[out] key On success, an identifier for the newly created
+ * key. For persistent keys, this is the key
+ * identifier defined in \p attributes.
+ * \c 0 on failure.
+ *
+ * \retval #PSA_SUCCESS
+ * Success.
+ * If the key is persistent, the key material and the key's metadata
+ * have been saved to persistent storage.
+ * \retval #PSA_ERROR_ALREADY_EXISTS
+ * This is an attempt to create a persistent key, and there is
+ * already a persistent key with the given identifier.
+ * \retval #PSA_ERROR_INSUFFICIENT_DATA
+ * There was not enough data to create the desired key.
+ * Note that in this case, no output is written to the output buffer.
+ * The operation's capacity is set to 0, thus subsequent calls to
+ * this function will not succeed, even with a smaller output buffer.
+ * \retval #PSA_ERROR_NOT_SUPPORTED
+ * The key type or key size is not supported, either by the
+ * implementation in general or in this particular location.
+ * \retval #PSA_ERROR_INVALID_ARGUMENT
+ * The provided key attributes are not valid for the operation.
+ * \retval #PSA_ERROR_NOT_PERMITTED
+ * The #PSA_KEY_DERIVATION_INPUT_SECRET or
+ * #PSA_KEY_DERIVATION_INPUT_PASSWORD input was not provided through a
+ * key; or one of the inputs was a key whose policy didn't allow
+ * #PSA_KEY_USAGE_DERIVE.
+ * \retval #PSA_ERROR_INSUFFICIENT_MEMORY \emptydescription
+ * \retval #PSA_ERROR_INSUFFICIENT_STORAGE \emptydescription
+ * \retval #PSA_ERROR_COMMUNICATION_FAILURE \emptydescription
+ * \retval #PSA_ERROR_HARDWARE_FAILURE \emptydescription
+ * \retval #PSA_ERROR_CORRUPTION_DETECTED \emptydescription
+ * \retval #PSA_ERROR_DATA_INVALID \emptydescription
+ * \retval #PSA_ERROR_DATA_CORRUPT \emptydescription
+ * \retval #PSA_ERROR_STORAGE_FAILURE \emptydescription
+ * \retval #PSA_ERROR_BAD_STATE
+ * The operation state is not valid (it must be active and completed
+ * all required input steps), or the library has not been previously
+ * initialized by psa_crypto_init().
+ * It is implementation-dependent whether a failure to initialize
+ * results in this error code.
+ */
+psa_status_t psa_key_derivation_output_key_custom(
+ const psa_key_attributes_t *attributes,
+ psa_key_derivation_operation_t *operation,
+ const psa_custom_key_parameters_t *custom,
+ const uint8_t *custom_data,
+ size_t custom_data_length,
+ mbedtls_svc_key_id_t *key);
+
+#ifndef __cplusplus
+/* Omitted when compiling in C++, because one of the parameters is a
+ * pointer to a struct with a flexible array member, and that is not
+ * standard C++.
+ * https://github.com/Mbed-TLS/mbedtls/issues/9020
+ */
+/** Derive a key from an ongoing key derivation operation with custom
+ * production parameters.
+ *
+ * \note
+ * This is a deprecated variant of psa_key_derivation_output_key_custom().
+ * It is equivalent except that the associated variable-length data
+ * is passed in `params->data` instead of a separate parameter.
+ * This function will be removed in a future version of Mbed TLS.
+ *
+ * \param[in] attributes The attributes for the new key.
+ * If the key type to be created is
+ * #PSA_KEY_TYPE_PASSWORD_HASH then the algorithm in
+ * the policy must be the same as in the current
+ * operation.
+ * \param[in,out] operation The key derivation operation object to read from.
* \param[in] params Customization parameters for the key derivation.
* When this is #PSA_KEY_PRODUCTION_PARAMETERS_INIT
* with \p params_data_length = 0,
@@ -3848,14 +3930,13 @@ psa_status_t psa_key_derivation_output_key(
* It is implementation-dependent whether a failure to initialize
* results in this error code.
*/
-#ifndef __cplusplus
psa_status_t psa_key_derivation_output_key_ext(
const psa_key_attributes_t *attributes,
psa_key_derivation_operation_t *operation,
const psa_key_production_parameters_t *params,
size_t params_data_length,
mbedtls_svc_key_id_t *key);
-#endif
+#endif /* !__cplusplus */
/** Compare output data from a key derivation operation to an expected value.
*
@@ -3881,8 +3962,8 @@ psa_status_t psa_key_derivation_output_key_ext(
* psa_key_derivation_abort().
*
* \param[in,out] operation The key derivation operation object to read from.
- * \param[in] expected_output Buffer containing the expected derivation output.
- * \param output_length Length of the expected output; this is also the
+ * \param[in] expected Buffer containing the expected derivation output.
+ * \param expected_length Length of the expected output; this is also the
* number of bytes that will be read.
*
* \retval #PSA_SUCCESS \emptydescription
@@ -3912,8 +3993,8 @@ psa_status_t psa_key_derivation_output_key_ext(
*/
psa_status_t psa_key_derivation_verify_bytes(
psa_key_derivation_operation_t *operation,
- const uint8_t *expected_output,
- size_t output_length);
+ const uint8_t *expected,
+ size_t expected_length);
/** Compare output data from a key derivation operation to an expected value
* stored in a key object.
@@ -3943,7 +4024,7 @@ psa_status_t psa_key_derivation_verify_bytes(
* operation. The value of this key was likely
* computed by a previous call to
* psa_key_derivation_output_key() or
- * psa_key_derivation_output_key_ext().
+ * psa_key_derivation_output_key_custom().
*
* \retval #PSA_SUCCESS \emptydescription
* \retval #PSA_ERROR_INVALID_SIGNATURE
@@ -4111,9 +4192,9 @@ psa_status_t psa_generate_random(uint8_t *output,
* between 2^{n-1} and 2^n where n is the bit size specified in the
* attributes.
*
- * \note This function is equivalent to calling psa_generate_key_ext()
- * with the production parameters #PSA_KEY_PRODUCTION_PARAMETERS_INIT
- * and `params_data_length == 0` (i.e. `params->data` is empty).
+ * \note This function is equivalent to calling psa_generate_key_custom()
+ * with the custom production parameters #PSA_CUSTOM_KEY_PARAMETERS_INIT
+ * and `custom_data_length == 0` (i.e. `custom_data` is empty).
*
* \param[in] attributes The attributes for the new key.
* \param[out] key On success, an identifier for the newly created
@@ -4153,7 +4234,7 @@ psa_status_t psa_generate_key(const psa_key_attributes_t *attributes,
* See the description of psa_generate_key() for the operation of this
* function with the default production parameters. In addition, this function
* supports the following production customizations, described in more detail
- * in the documentation of ::psa_key_production_parameters_t:
+ * in the documentation of ::psa_custom_key_parameters_t:
*
* - RSA keys: generation with a custom public exponent.
*
@@ -4161,6 +4242,64 @@ psa_status_t psa_generate_key(const psa_key_attributes_t *attributes,
* versions of Mbed TLS.
*
* \param[in] attributes The attributes for the new key.
+ * \param[in] custom Customization parameters for the key generation.
+ * When this is #PSA_CUSTOM_KEY_PARAMETERS_INIT
+ * with \p custom_data_length = 0,
+ * this function is equivalent to
+ * psa_generate_key().
+ * \param[in] custom_data Variable-length data associated with \c custom.
+ * \param custom_data_length
+ * Length of `custom_data` in bytes.
+ * \param[out] key On success, an identifier for the newly created
+ * key. For persistent keys, this is the key
+ * identifier defined in \p attributes.
+ * \c 0 on failure.
+ *
+ * \retval #PSA_SUCCESS
+ * Success.
+ * If the key is persistent, the key material and the key's metadata
+ * have been saved to persistent storage.
+ * \retval #PSA_ERROR_ALREADY_EXISTS
+ * This is an attempt to create a persistent key, and there is
+ * already a persistent key with the given identifier.
+ * \retval #PSA_ERROR_NOT_SUPPORTED \emptydescription
+ * \retval #PSA_ERROR_INVALID_ARGUMENT \emptydescription
+ * \retval #PSA_ERROR_INSUFFICIENT_MEMORY \emptydescription
+ * \retval #PSA_ERROR_INSUFFICIENT_ENTROPY \emptydescription
+ * \retval #PSA_ERROR_COMMUNICATION_FAILURE \emptydescription
+ * \retval #PSA_ERROR_HARDWARE_FAILURE \emptydescription
+ * \retval #PSA_ERROR_CORRUPTION_DETECTED \emptydescription
+ * \retval #PSA_ERROR_INSUFFICIENT_STORAGE \emptydescription
+ * \retval #PSA_ERROR_DATA_INVALID \emptydescription
+ * \retval #PSA_ERROR_DATA_CORRUPT \emptydescription
+ * \retval #PSA_ERROR_STORAGE_FAILURE \emptydescription
+ * \retval #PSA_ERROR_BAD_STATE
+ * The library has not been previously initialized by psa_crypto_init().
+ * It is implementation-dependent whether a failure to initialize
+ * results in this error code.
+ */
+psa_status_t psa_generate_key_custom(const psa_key_attributes_t *attributes,
+ const psa_custom_key_parameters_t *custom,
+ const uint8_t *custom_data,
+ size_t custom_data_length,
+ mbedtls_svc_key_id_t *key);
+
+#ifndef __cplusplus
+/* Omitted when compiling in C++, because one of the parameters is a
+ * pointer to a struct with a flexible array member, and that is not
+ * standard C++.
+ * https://github.com/Mbed-TLS/mbedtls/issues/9020
+ */
+/**
+ * \brief Generate a key or key pair using custom production parameters.
+ *
+ * \note
+ * This is a deprecated variant of psa_key_derivation_output_key_custom().
+ * It is equivalent except that the associated variable-length data
+ * is passed in `params->data` instead of a separate parameter.
+ * This function will be removed in a future version of Mbed TLS.
+ *
+ * \param[in] attributes The attributes for the new key.
* \param[in] params Customization parameters for the key generation.
* When this is #PSA_KEY_PRODUCTION_PARAMETERS_INIT
* with \p params_data_length = 0,
@@ -4196,12 +4335,11 @@ psa_status_t psa_generate_key(const psa_key_attributes_t *attributes,
* It is implementation-dependent whether a failure to initialize
* results in this error code.
*/
-#ifndef __cplusplus
psa_status_t psa_generate_key_ext(const psa_key_attributes_t *attributes,
const psa_key_production_parameters_t *params,
size_t params_data_length,
mbedtls_svc_key_id_t *key);
-#endif
+#endif /* !__cplusplus */
/**@}*/
diff --git a/thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h b/thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h
index 63fb29e85b..3a2af15180 100644
--- a/thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h
+++ b/thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h
@@ -2,6 +2,8 @@
* \file psa/crypto_adjust_auto_enabled.h
* \brief Adjust PSA configuration: enable always-on features
*
+ * This is an internal header. Do not include it directly.
+ *
* Always enable certain features which require a negligible amount of code
* to implement, to avoid some edge cases in the configuration combinatorics.
*/
@@ -13,6 +15,14 @@
#ifndef PSA_CRYPTO_ADJUST_AUTO_ENABLED_H
#define PSA_CRYPTO_ADJUST_AUTO_ENABLED_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include psa/crypto_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
#define PSA_WANT_KEY_TYPE_DERIVE 1
#define PSA_WANT_KEY_TYPE_PASSWORD 1
#define PSA_WANT_KEY_TYPE_PASSWORD_HASH 1
diff --git a/thirdparty/mbedtls/include/psa/crypto_adjust_config_dependencies.h b/thirdparty/mbedtls/include/psa/crypto_adjust_config_dependencies.h
new file mode 100644
index 0000000000..92e9c4de28
--- /dev/null
+++ b/thirdparty/mbedtls/include/psa/crypto_adjust_config_dependencies.h
@@ -0,0 +1,51 @@
+/**
+ * \file psa/crypto_adjust_config_dependencies.h
+ * \brief Adjust PSA configuration by resolving some dependencies.
+ *
+ * This is an internal header. Do not include it directly.
+ *
+ * See docs/proposed/psa-conditional-inclusion-c.md.
+ * If the Mbed TLS implementation of a cryptographic mechanism A depends on a
+ * cryptographic mechanism B then if the cryptographic mechanism A is enabled
+ * and not accelerated enable B. Note that if A is enabled and accelerated, it
+ * is not necessary to enable B for A support.
+ */
+/*
+ * Copyright The Mbed TLS Contributors
+ * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+ */
+
+#ifndef PSA_CRYPTO_ADJUST_CONFIG_DEPENDENCIES_H
+#define PSA_CRYPTO_ADJUST_CONFIG_DEPENDENCIES_H
+
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include psa/crypto_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
+#if (defined(PSA_WANT_ALG_TLS12_PRF) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_TLS12_PRF)) || \
+ (defined(PSA_WANT_ALG_TLS12_PSK_TO_MS) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_TLS12_PSK_TO_MS)) || \
+ (defined(PSA_WANT_ALG_HKDF) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_HKDF)) || \
+ (defined(PSA_WANT_ALG_HKDF_EXTRACT) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_HKDF_EXTRACT)) || \
+ (defined(PSA_WANT_ALG_HKDF_EXPAND) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_HKDF_EXPAND)) || \
+ (defined(PSA_WANT_ALG_PBKDF2_HMAC) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_PBKDF2_HMAC))
+#define PSA_WANT_ALG_HMAC 1
+#define PSA_WANT_KEY_TYPE_HMAC 1
+#endif
+
+#if (defined(PSA_WANT_ALG_PBKDF2_AES_CMAC_PRF_128) && \
+ !defined(MBEDTLS_PSA_ACCEL_ALG_PBKDF2_AES_CMAC_PRF_128))
+#define PSA_WANT_KEY_TYPE_AES 1
+#define PSA_WANT_ALG_CMAC 1
+#endif
+
+#endif /* PSA_CRYPTO_ADJUST_CONFIG_DEPENDENCIES_H */
diff --git a/thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h b/thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h
index 63afc0e402..cec39e01ce 100644
--- a/thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h
+++ b/thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h
@@ -2,6 +2,8 @@
* \file psa/crypto_adjust_config_key_pair_types.h
* \brief Adjust PSA configuration for key pair types.
*
+ * This is an internal header. Do not include it directly.
+ *
* See docs/proposed/psa-conditional-inclusion-c.md.
* - Support non-basic operations in a keypair type implicitly enables basic
* support for that keypair type.
@@ -19,6 +21,14 @@
#ifndef PSA_CRYPTO_ADJUST_KEYPAIR_TYPES_H
#define PSA_CRYPTO_ADJUST_KEYPAIR_TYPES_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include psa/crypto_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/*****************************************************************
* ANYTHING -> BASIC
****************************************************************/
diff --git a/thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h b/thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h
index 332b622c9b..54b116f434 100644
--- a/thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h
+++ b/thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h
@@ -2,6 +2,8 @@
* \file psa/crypto_adjust_config_synonyms.h
* \brief Adjust PSA configuration: enable quasi-synonyms
*
+ * This is an internal header. Do not include it directly.
+ *
* When two features require almost the same code, we automatically enable
* both when either one is requested, to reduce the combinatorics of
* possible configurations.
@@ -14,6 +16,14 @@
#ifndef PSA_CRYPTO_ADJUST_CONFIG_SYNONYMS_H
#define PSA_CRYPTO_ADJUST_CONFIG_SYNONYMS_H
+#if !defined(MBEDTLS_CONFIG_FILES_READ)
+#error "Do not include psa/crypto_adjust_*.h manually! This can lead to problems, " \
+ "up to and including runtime errors such as buffer overflows. " \
+ "If you're trying to fix a complaint from check_config.h, just remove " \
+ "it from your configuration file: since Mbed TLS 3.0, it is included " \
+ "automatically at the right point."
+#endif /* */
+
/****************************************************************/
/* De facto synonyms */
/****************************************************************/
diff --git a/thirdparty/mbedtls/include/psa/crypto_extra.h b/thirdparty/mbedtls/include/psa/crypto_extra.h
index bd985e9b78..d276cd4c7f 100644
--- a/thirdparty/mbedtls/include/psa/crypto_extra.h
+++ b/thirdparty/mbedtls/include/psa/crypto_extra.h
@@ -154,6 +154,14 @@ static inline void psa_clear_key_slot_number(
* specified in \p attributes.
*
* \param[in] attributes The attributes of the existing key.
+ * - The lifetime must be a persistent lifetime
+ * in a secure element. Volatile lifetimes are
+ * not currently supported.
+ * - The key identifier must be in the valid
+ * range for persistent keys.
+ * - The key type and size must be specified and
+ * must be consistent with the key material
+ * in the secure element.
*
* \retval #PSA_SUCCESS
* The key was successfully registered.
@@ -479,7 +487,7 @@ psa_status_t mbedtls_psa_external_get_random(
* #PSA_KEY_ID_VENDOR_MIN and #PSA_KEY_ID_VENDOR_MAX and must not intersect
* with any other set of implementation-chosen key identifiers.
*
- * This value is part of the library's ABI since changing it would invalidate
+ * This value is part of the library's API since changing it would invalidate
* the values of built-in key identifiers in applications.
*/
#define MBEDTLS_PSA_KEY_ID_BUILTIN_MIN ((psa_key_id_t) 0x7fff0000)
diff --git a/thirdparty/mbedtls/include/psa/crypto_struct.h b/thirdparty/mbedtls/include/psa/crypto_struct.h
index e2c227b2eb..362e921a36 100644
--- a/thirdparty/mbedtls/include/psa/crypto_struct.h
+++ b/thirdparty/mbedtls/include/psa/crypto_struct.h
@@ -223,13 +223,36 @@ static inline struct psa_key_derivation_s psa_key_derivation_operation_init(
return v;
}
+struct psa_custom_key_parameters_s {
+ /* Future versions may add other fields in this structure. */
+ uint32_t flags;
+};
+
+/** The default production parameters for key generation or key derivation.
+ *
+ * Calling psa_generate_key_custom() or psa_key_derivation_output_key_custom()
+ * with `custom=PSA_CUSTOM_KEY_PARAMETERS_INIT` and `custom_data_length=0` is
+ * equivalent to calling psa_generate_key() or psa_key_derivation_output_key()
+ * respectively.
+ */
+#define PSA_CUSTOM_KEY_PARAMETERS_INIT { 0 }
+
#ifndef __cplusplus
+/* Omitted when compiling in C++, because one of the parameters is a
+ * pointer to a struct with a flexible array member, and that is not
+ * standard C++.
+ * https://github.com/Mbed-TLS/mbedtls/issues/9020
+ */
+/* This is a deprecated variant of `struct psa_custom_key_parameters_s`.
+ * It has exactly the same layout, plus an extra field which is a flexible
+ * array member. Thus a `const struct psa_key_production_parameters_s *`
+ * can be passed to any function that reads a
+ * `const struct psa_custom_key_parameters_s *`.
+ */
struct psa_key_production_parameters_s {
- /* Future versions may add other fields in this structure. */
uint32_t flags;
uint8_t data[];
};
-#endif
/** The default production parameters for key generation or key derivation.
*
@@ -240,6 +263,7 @@ struct psa_key_production_parameters_s {
* respectively.
*/
#define PSA_KEY_PRODUCTION_PARAMETERS_INIT { 0 }
+#endif /* !__cplusplus */
struct psa_key_policy_s {
psa_key_usage_t MBEDTLS_PRIVATE(usage);
diff --git a/thirdparty/mbedtls/include/psa/crypto_types.h b/thirdparty/mbedtls/include/psa/crypto_types.h
index a36b6ee65d..f831486f4e 100644
--- a/thirdparty/mbedtls/include/psa/crypto_types.h
+++ b/thirdparty/mbedtls/include/psa/crypto_types.h
@@ -457,6 +457,30 @@ typedef uint16_t psa_key_derivation_step_t;
/** \brief Custom parameters for key generation or key derivation.
*
+ * This is a structure type with at least the following field:
+ *
+ * - \c flags: an unsigned integer type. 0 for the default production parameters.
+ *
+ * Functions that take such a structure as input also take an associated
+ * input buffer \c custom_data of length \c custom_data_length.
+ *
+ * The interpretation of this structure and the associated \c custom_data
+ * parameter depend on the type of the created key.
+ *
+ * - #PSA_KEY_TYPE_RSA_KEY_PAIR:
+ * - \c flags: must be 0.
+ * - \c custom_data: the public exponent, in little-endian order.
+ * This must be an odd integer and must not be 1.
+ * Implementations must support 65537, should support 3 and may
+ * support other values.
+ * When not using a driver, Mbed TLS supports values up to \c INT_MAX.
+ * If this is empty, the default value 65537 is used.
+ * - Other key types: reserved for future use. \c flags must be 0.
+ */
+typedef struct psa_custom_key_parameters_s psa_custom_key_parameters_t;
+
+/** \brief Custom parameters for key generation or key derivation.
+ *
* This is a structure type with at least the following fields:
*
* - \c flags: an unsigned integer type. 0 for the default production parameters.
@@ -477,9 +501,7 @@ typedef uint16_t psa_key_derivation_step_t;
* - Other key types: reserved for future use. \c flags must be 0.
*
*/
-#ifndef __cplusplus
typedef struct psa_key_production_parameters_s psa_key_production_parameters_t;
-#endif
/**@}*/
diff --git a/thirdparty/mbedtls/library/bignum.c b/thirdparty/mbedtls/library/bignum.c
index c45fd5bf24..424490951d 100644
--- a/thirdparty/mbedtls/library/bignum.c
+++ b/thirdparty/mbedtls/library/bignum.c
@@ -27,6 +27,7 @@
#include "mbedtls/bignum.h"
#include "bignum_core.h"
+#include "bignum_internal.h"
#include "bn_mul.h"
#include "mbedtls/platform_util.h"
#include "mbedtls/error.h"
@@ -1610,9 +1611,13 @@ int mbedtls_mpi_mod_int(mbedtls_mpi_uint *r, const mbedtls_mpi *A, mbedtls_mpi_s
return 0;
}
-int mbedtls_mpi_exp_mod(mbedtls_mpi *X, const mbedtls_mpi *A,
- const mbedtls_mpi *E, const mbedtls_mpi *N,
- mbedtls_mpi *prec_RR)
+/*
+ * Warning! If the parameter E_public has MBEDTLS_MPI_IS_PUBLIC as its value,
+ * this function is not constant time with respect to the exponent (parameter E).
+ */
+static int mbedtls_mpi_exp_mod_optionally_safe(mbedtls_mpi *X, const mbedtls_mpi *A,
+ const mbedtls_mpi *E, int E_public,
+ const mbedtls_mpi *N, mbedtls_mpi *prec_RR)
{
int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
@@ -1695,7 +1700,11 @@ int mbedtls_mpi_exp_mod(mbedtls_mpi *X, const mbedtls_mpi *A,
{
mbedtls_mpi_uint mm = mbedtls_mpi_core_montmul_init(N->p);
mbedtls_mpi_core_to_mont_rep(X->p, X->p, N->p, N->n, mm, RR.p, T);
- mbedtls_mpi_core_exp_mod(X->p, X->p, N->p, N->n, E->p, E->n, RR.p, T);
+ if (E_public == MBEDTLS_MPI_IS_PUBLIC) {
+ mbedtls_mpi_core_exp_mod_unsafe(X->p, X->p, N->p, N->n, E->p, E->n, RR.p, T);
+ } else {
+ mbedtls_mpi_core_exp_mod(X->p, X->p, N->p, N->n, E->p, E->n, RR.p, T);
+ }
mbedtls_mpi_core_from_mont_rep(X->p, X->p, N->p, N->n, mm, T);
}
@@ -1720,6 +1729,20 @@ cleanup:
return ret;
}
+int mbedtls_mpi_exp_mod(mbedtls_mpi *X, const mbedtls_mpi *A,
+ const mbedtls_mpi *E, const mbedtls_mpi *N,
+ mbedtls_mpi *prec_RR)
+{
+ return mbedtls_mpi_exp_mod_optionally_safe(X, A, E, MBEDTLS_MPI_IS_SECRET, N, prec_RR);
+}
+
+int mbedtls_mpi_exp_mod_unsafe(mbedtls_mpi *X, const mbedtls_mpi *A,
+ const mbedtls_mpi *E, const mbedtls_mpi *N,
+ mbedtls_mpi *prec_RR)
+{
+ return mbedtls_mpi_exp_mod_optionally_safe(X, A, E, MBEDTLS_MPI_IS_PUBLIC, N, prec_RR);
+}
+
/*
* Greatest common divisor: G = gcd(A, B) (HAC 14.54)
*/
diff --git a/thirdparty/mbedtls/library/bignum_core.c b/thirdparty/mbedtls/library/bignum_core.c
index 1a3e0b9b6f..4231554b84 100644
--- a/thirdparty/mbedtls/library/bignum_core.c
+++ b/thirdparty/mbedtls/library/bignum_core.c
@@ -746,8 +746,94 @@ static void exp_mod_precompute_window(const mbedtls_mpi_uint *A,
}
}
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+// Set to a default that is neither MBEDTLS_MPI_IS_PUBLIC nor MBEDTLS_MPI_IS_SECRET
+int mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_PUBLIC + MBEDTLS_MPI_IS_SECRET + 1;
+#endif
+
+/*
+ * This function calculates the indices of the exponent where the exponentiation algorithm should
+ * start processing.
+ *
+ * Warning! If the parameter E_public has MBEDTLS_MPI_IS_PUBLIC as its value,
+ * this function is not constant time with respect to the exponent (parameter E).
+ */
+static inline void exp_mod_calc_first_bit_optionally_safe(const mbedtls_mpi_uint *E,
+ size_t E_limbs,
+ int E_public,
+ size_t *E_limb_index,
+ size_t *E_bit_index)
+{
+ if (E_public == MBEDTLS_MPI_IS_PUBLIC) {
+ /*
+ * Skip leading zero bits.
+ */
+ size_t E_bits = mbedtls_mpi_core_bitlen(E, E_limbs);
+ if (E_bits == 0) {
+ /*
+ * If E is 0 mbedtls_mpi_core_bitlen() returns 0. Even if that is the case, we will want
+ * to represent it as a single 0 bit and as such the bitlength will be 1.
+ */
+ E_bits = 1;
+ }
+
+ *E_limb_index = E_bits / biL;
+ *E_bit_index = E_bits % biL;
+
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+ mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_PUBLIC;
+#endif
+ } else {
+ /*
+ * Here we need to be constant time with respect to E and can't do anything better than
+ * start at the first allocated bit.
+ */
+ *E_limb_index = E_limbs;
+ *E_bit_index = 0;
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+ // Only mark the codepath safe if there wasn't an unsafe codepath before
+ if (mbedtls_mpi_optionally_safe_codepath != MBEDTLS_MPI_IS_PUBLIC) {
+ mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_SECRET;
+ }
+#endif
+ }
+}
+
+/*
+ * Warning! If the parameter window_public has MBEDTLS_MPI_IS_PUBLIC as its value, this function is
+ * not constant time with respect to the window parameter and consequently the exponent of the
+ * exponentiation (parameter E of mbedtls_mpi_core_exp_mod_optionally_safe).
+ */
+static inline void exp_mod_table_lookup_optionally_safe(mbedtls_mpi_uint *Wselect,
+ mbedtls_mpi_uint *Wtable,
+ size_t AN_limbs, size_t welem,
+ mbedtls_mpi_uint window,
+ int window_public)
+{
+ if (window_public == MBEDTLS_MPI_IS_PUBLIC) {
+ memcpy(Wselect, Wtable + window * AN_limbs, AN_limbs * ciL);
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+ mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_PUBLIC;
+#endif
+ } else {
+ /* Select Wtable[window] without leaking window through
+ * memory access patterns. */
+ mbedtls_mpi_core_ct_uint_table_lookup(Wselect, Wtable,
+ AN_limbs, welem, window);
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+ // Only mark the codepath safe if there wasn't an unsafe codepath before
+ if (mbedtls_mpi_optionally_safe_codepath != MBEDTLS_MPI_IS_PUBLIC) {
+ mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_SECRET;
+ }
+#endif
+ }
+}
+
/* Exponentiation: X := A^E mod N.
*
+ * Warning! If the parameter E_public has MBEDTLS_MPI_IS_PUBLIC as its value,
+ * this function is not constant time with respect to the exponent (parameter E).
+ *
* A must already be in Montgomery form.
*
* As in other bignum functions, assume that AN_limbs and E_limbs are nonzero.
@@ -758,16 +844,25 @@ static void exp_mod_precompute_window(const mbedtls_mpi_uint *A,
* (The difference is that the body in our loop processes a single bit instead
* of a full window.)
*/
-void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
- const mbedtls_mpi_uint *A,
- const mbedtls_mpi_uint *N,
- size_t AN_limbs,
- const mbedtls_mpi_uint *E,
- size_t E_limbs,
- const mbedtls_mpi_uint *RR,
- mbedtls_mpi_uint *T)
+static void mbedtls_mpi_core_exp_mod_optionally_safe(mbedtls_mpi_uint *X,
+ const mbedtls_mpi_uint *A,
+ const mbedtls_mpi_uint *N,
+ size_t AN_limbs,
+ const mbedtls_mpi_uint *E,
+ size_t E_limbs,
+ int E_public,
+ const mbedtls_mpi_uint *RR,
+ mbedtls_mpi_uint *T)
{
- const size_t wsize = exp_mod_get_window_size(E_limbs * biL);
+ /* We'll process the bits of E from most significant
+ * (limb_index=E_limbs-1, E_bit_index=biL-1) to least significant
+ * (limb_index=0, E_bit_index=0). */
+ size_t E_limb_index;
+ size_t E_bit_index;
+ exp_mod_calc_first_bit_optionally_safe(E, E_limbs, E_public,
+ &E_limb_index, &E_bit_index);
+
+ const size_t wsize = exp_mod_get_window_size(E_limb_index * biL);
const size_t welem = ((size_t) 1) << wsize;
/* This is how we will use the temporary storage T, which must have space
@@ -786,7 +881,7 @@ void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
const mbedtls_mpi_uint mm = mbedtls_mpi_core_montmul_init(N);
- /* Set Wtable[i] = A^(2^i) (in Montgomery representation) */
+ /* Set Wtable[i] = A^i (in Montgomery representation) */
exp_mod_precompute_window(A, N, AN_limbs,
mm, RR,
welem, Wtable, temp);
@@ -798,11 +893,6 @@ void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
/* X = 1 (in Montgomery presentation) initially */
memcpy(X, Wtable, AN_limbs * ciL);
- /* We'll process the bits of E from most significant
- * (limb_index=E_limbs-1, E_bit_index=biL-1) to least significant
- * (limb_index=0, E_bit_index=0). */
- size_t E_limb_index = E_limbs;
- size_t E_bit_index = 0;
/* At any given time, window contains window_bits bits from E.
* window_bits can go up to wsize. */
size_t window_bits = 0;
@@ -828,10 +918,9 @@ void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
* when we've finished processing the exponent. */
if (window_bits == wsize ||
(E_bit_index == 0 && E_limb_index == 0)) {
- /* Select Wtable[window] without leaking window through
- * memory access patterns. */
- mbedtls_mpi_core_ct_uint_table_lookup(Wselect, Wtable,
- AN_limbs, welem, window);
+
+ exp_mod_table_lookup_optionally_safe(Wselect, Wtable, AN_limbs, welem,
+ window, E_public);
/* Multiply X by the selected element. */
mbedtls_mpi_core_montmul(X, X, Wselect, AN_limbs, N, AN_limbs, mm,
temp);
@@ -841,6 +930,42 @@ void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
} while (!(E_bit_index == 0 && E_limb_index == 0));
}
+void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X,
+ const mbedtls_mpi_uint *A,
+ const mbedtls_mpi_uint *N, size_t AN_limbs,
+ const mbedtls_mpi_uint *E, size_t E_limbs,
+ const mbedtls_mpi_uint *RR,
+ mbedtls_mpi_uint *T)
+{
+ mbedtls_mpi_core_exp_mod_optionally_safe(X,
+ A,
+ N,
+ AN_limbs,
+ E,
+ E_limbs,
+ MBEDTLS_MPI_IS_SECRET,
+ RR,
+ T);
+}
+
+void mbedtls_mpi_core_exp_mod_unsafe(mbedtls_mpi_uint *X,
+ const mbedtls_mpi_uint *A,
+ const mbedtls_mpi_uint *N, size_t AN_limbs,
+ const mbedtls_mpi_uint *E, size_t E_limbs,
+ const mbedtls_mpi_uint *RR,
+ mbedtls_mpi_uint *T)
+{
+ mbedtls_mpi_core_exp_mod_optionally_safe(X,
+ A,
+ N,
+ AN_limbs,
+ E,
+ E_limbs,
+ MBEDTLS_MPI_IS_PUBLIC,
+ RR,
+ T);
+}
+
mbedtls_mpi_uint mbedtls_mpi_core_sub_int(mbedtls_mpi_uint *X,
const mbedtls_mpi_uint *A,
mbedtls_mpi_uint c, /* doubles as carry */
diff --git a/thirdparty/mbedtls/library/bignum_core.h b/thirdparty/mbedtls/library/bignum_core.h
index 92c8d47db5..cf6485a148 100644
--- a/thirdparty/mbedtls/library/bignum_core.h
+++ b/thirdparty/mbedtls/library/bignum_core.h
@@ -90,6 +90,27 @@
#define GET_BYTE(X, i) \
(((X)[(i) / ciL] >> (((i) % ciL) * 8)) & 0xff)
+/* Constants to identify whether a value is public or secret. If a parameter is marked as secret by
+ * this constant, the function must be constant time with respect to the parameter.
+ *
+ * This is only needed for functions with the _optionally_safe postfix. All other functions have
+ * fixed behavior that can't be changed at runtime and are constant time with respect to their
+ * parameters as prescribed by their documentation or by conventions in their module's documentation.
+ *
+ * Parameters should be named X_public where X is the name of the
+ * corresponding input parameter.
+ *
+ * Implementation should always check using
+ * if (X_public == MBEDTLS_MPI_IS_PUBLIC) {
+ * // unsafe path
+ * } else {
+ * // safe path
+ * }
+ * not the other way round, in order to prevent misuse. (This is, if a value
+ * other than the two below is passed, default to the safe path.) */
+#define MBEDTLS_MPI_IS_PUBLIC 0x2a2a2a2a
+#define MBEDTLS_MPI_IS_SECRET 0
+
/** Count leading zero bits in a given integer.
*
* \warning The result is undefined if \p a == 0
@@ -605,6 +626,42 @@ int mbedtls_mpi_core_random(mbedtls_mpi_uint *X,
size_t mbedtls_mpi_core_exp_mod_working_limbs(size_t AN_limbs, size_t E_limbs);
/**
+ * \brief Perform a modular exponentiation with public or secret exponent:
+ * X = A^E mod N, where \p A is already in Montgomery form.
+ *
+ * \warning This function is not constant time with respect to \p E (the exponent).
+ *
+ * \p X may be aliased to \p A, but not to \p RR or \p E, even if \p E_limbs ==
+ * \p AN_limbs.
+ *
+ * \param[out] X The destination MPI, as a little endian array of length
+ * \p AN_limbs.
+ * \param[in] A The base MPI, as a little endian array of length \p AN_limbs.
+ * Must be in Montgomery form.
+ * \param[in] N The modulus, as a little endian array of length \p AN_limbs.
+ * \param AN_limbs The number of limbs in \p X, \p A, \p N, \p RR.
+ * \param[in] E The exponent, as a little endian array of length \p E_limbs.
+ * \param E_limbs The number of limbs in \p E.
+ * \param[in] RR The precomputed residue of 2^{2*biL} modulo N, as a little
+ * endian array of length \p AN_limbs.
+ * \param[in,out] T Temporary storage of at least the number of limbs returned
+ * by `mbedtls_mpi_core_exp_mod_working_limbs()`.
+ * Its initial content is unused and its final content is
+ * indeterminate.
+ * It must not alias or otherwise overlap any of the other
+ * parameters.
+ * It is up to the caller to zeroize \p T when it is no
+ * longer needed, and before freeing it if it was dynamically
+ * allocated.
+ */
+void mbedtls_mpi_core_exp_mod_unsafe(mbedtls_mpi_uint *X,
+ const mbedtls_mpi_uint *A,
+ const mbedtls_mpi_uint *N, size_t AN_limbs,
+ const mbedtls_mpi_uint *E, size_t E_limbs,
+ const mbedtls_mpi_uint *RR,
+ mbedtls_mpi_uint *T);
+
+/**
* \brief Perform a modular exponentiation with secret exponent:
* X = A^E mod N, where \p A is already in Montgomery form.
*
@@ -760,4 +817,17 @@ void mbedtls_mpi_core_from_mont_rep(mbedtls_mpi_uint *X,
mbedtls_mpi_uint mm,
mbedtls_mpi_uint *T);
+/*
+ * Can't define thread local variables with our abstraction layer: do nothing if threading is on.
+ */
+#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C)
+extern int mbedtls_mpi_optionally_safe_codepath;
+
+static inline void mbedtls_mpi_optionally_safe_codepath_reset(void)
+{
+ // Set to a default that is neither MBEDTLS_MPI_IS_PUBLIC nor MBEDTLS_MPI_IS_SECRET
+ mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_PUBLIC + MBEDTLS_MPI_IS_SECRET + 1;
+}
+#endif
+
#endif /* MBEDTLS_BIGNUM_CORE_H */
diff --git a/thirdparty/mbedtls/library/bignum_internal.h b/thirdparty/mbedtls/library/bignum_internal.h
new file mode 100644
index 0000000000..aceaf55ea2
--- /dev/null
+++ b/thirdparty/mbedtls/library/bignum_internal.h
@@ -0,0 +1,50 @@
+/**
+ * \file bignum_internal.h
+ *
+ * \brief Internal-only bignum public-key cryptosystem API.
+ *
+ * This file declares bignum-related functions that are to be used
+ * only from within the Mbed TLS library itself.
+ *
+ */
+/*
+ * Copyright The Mbed TLS Contributors
+ * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+ */
+#ifndef MBEDTLS_BIGNUM_INTERNAL_H
+#define MBEDTLS_BIGNUM_INTERNAL_H
+
+/**
+ * \brief Perform a modular exponentiation: X = A^E mod N
+ *
+ * \warning This function is not constant time with respect to \p E (the exponent).
+ *
+ * \param X The destination MPI. This must point to an initialized MPI.
+ * This must not alias E or N.
+ * \param A The base of the exponentiation.
+ * This must point to an initialized MPI.
+ * \param E The exponent MPI. This must point to an initialized MPI.
+ * \param N The base for the modular reduction. This must point to an
+ * initialized MPI.
+ * \param prec_RR A helper MPI depending solely on \p N which can be used to
+ * speed-up multiple modular exponentiations for the same value
+ * of \p N. This may be \c NULL. If it is not \c NULL, it must
+ * point to an initialized MPI. If it hasn't been used after
+ * the call to mbedtls_mpi_init(), this function will compute
+ * the helper value and store it in \p prec_RR for reuse on
+ * subsequent calls to this function. Otherwise, the function
+ * will assume that \p prec_RR holds the helper value set by a
+ * previous call to mbedtls_mpi_exp_mod(), and reuse it.
+ *
+ * \return \c 0 if successful.
+ * \return #MBEDTLS_ERR_MPI_ALLOC_FAILED if a memory allocation failed.
+ * \return #MBEDTLS_ERR_MPI_BAD_INPUT_DATA if \c N is negative or
+ * even, or if \c E is negative.
+ * \return Another negative error code on different kinds of failures.
+ *
+ */
+int mbedtls_mpi_exp_mod_unsafe(mbedtls_mpi *X, const mbedtls_mpi *A,
+ const mbedtls_mpi *E, const mbedtls_mpi *N,
+ mbedtls_mpi *prec_RR);
+
+#endif /* bignum_internal.h */
diff --git a/thirdparty/mbedtls/library/block_cipher.c b/thirdparty/mbedtls/library/block_cipher.c
index 04cd7fb444..51cdcdf46b 100644
--- a/thirdparty/mbedtls/library/block_cipher.c
+++ b/thirdparty/mbedtls/library/block_cipher.c
@@ -51,6 +51,10 @@ static int mbedtls_cipher_error_from_psa(psa_status_t status)
void mbedtls_block_cipher_free(mbedtls_block_cipher_context_t *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
#if defined(MBEDTLS_BLOCK_CIPHER_SOME_PSA)
if (ctx->engine == MBEDTLS_BLOCK_CIPHER_ENGINE_PSA) {
psa_destroy_key(ctx->psa_key_id);
diff --git a/thirdparty/mbedtls/library/cipher.c b/thirdparty/mbedtls/library/cipher.c
index 0683677eda..7f4c121492 100644
--- a/thirdparty/mbedtls/library/cipher.c
+++ b/thirdparty/mbedtls/library/cipher.c
@@ -849,6 +849,9 @@ static int get_pkcs_padding(unsigned char *input, size_t input_len,
}
padding_len = input[input_len - 1];
+ if (padding_len == 0 || padding_len > input_len) {
+ return MBEDTLS_ERR_CIPHER_INVALID_PADDING;
+ }
*data_len = input_len - padding_len;
mbedtls_ct_condition_t bad = mbedtls_ct_uint_gt(padding_len, input_len);
diff --git a/thirdparty/mbedtls/library/common.h b/thirdparty/mbedtls/library/common.h
index 3936ffdfe1..7bb2674293 100644
--- a/thirdparty/mbedtls/library/common.h
+++ b/thirdparty/mbedtls/library/common.h
@@ -352,17 +352,19 @@ static inline void mbedtls_xor_no_simd(unsigned char *r,
#endif
/* Always provide a static assert macro, so it can be used unconditionally.
- * It will expand to nothing on some systems.
- * Can be used outside functions (but don't add a trailing ';' in that case:
- * the semicolon is included here to avoid triggering -Wextra-semi when
- * MBEDTLS_STATIC_ASSERT() expands to nothing).
- * Can't use the C11-style `defined(static_assert)` on FreeBSD, since it
+ * It does nothing on systems where we don't know how to define a static assert.
+ */
+/* Can't use the C11-style `defined(static_assert)` on FreeBSD, since it
* defines static_assert even with -std=c99, but then complains about it.
*/
#if defined(static_assert) && !defined(__FreeBSD__)
-#define MBEDTLS_STATIC_ASSERT(expr, msg) static_assert(expr, msg);
+#define MBEDTLS_STATIC_ASSERT(expr, msg) static_assert(expr, msg)
#else
-#define MBEDTLS_STATIC_ASSERT(expr, msg)
+/* Make sure `MBEDTLS_STATIC_ASSERT(expr, msg);` is valid both inside and
+ * outside a function. We choose a struct declaration, which can be repeated
+ * any number of times and does not need a matching definition. */
+#define MBEDTLS_STATIC_ASSERT(expr, msg) \
+ struct ISO_C_does_not_allow_extra_semicolon_outside_of_a_function
#endif
#if defined(__has_builtin)
diff --git a/thirdparty/mbedtls/library/ctr_drbg.c b/thirdparty/mbedtls/library/ctr_drbg.c
index 66d9d28c58..b82044eb7d 100644
--- a/thirdparty/mbedtls/library/ctr_drbg.c
+++ b/thirdparty/mbedtls/library/ctr_drbg.c
@@ -26,13 +26,13 @@
#endif
/* Using error translation functions from PSA to MbedTLS */
-#if !defined(MBEDTLS_AES_C)
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
#include "psa_util_internal.h"
#endif
#include "mbedtls/platform.h"
-#if !defined(MBEDTLS_AES_C)
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
static psa_status_t ctr_drbg_setup_psa_context(mbedtls_ctr_drbg_psa_context *psa_ctx,
unsigned char *key, size_t key_len)
{
@@ -73,11 +73,11 @@ static void ctr_drbg_destroy_psa_contex(mbedtls_ctr_drbg_psa_context *psa_ctx)
void mbedtls_ctr_drbg_init(mbedtls_ctr_drbg_context *ctx)
{
memset(ctx, 0, sizeof(mbedtls_ctr_drbg_context));
-#if defined(MBEDTLS_AES_C)
- mbedtls_aes_init(&ctx->aes_ctx);
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
ctx->psa_ctx.key_id = MBEDTLS_SVC_KEY_ID_INIT;
ctx->psa_ctx.operation = psa_cipher_operation_init();
+#else
+ mbedtls_aes_init(&ctx->aes_ctx);
#endif
/* Indicate that the entropy nonce length is not set explicitly.
* See mbedtls_ctr_drbg_set_nonce_len(). */
@@ -102,10 +102,10 @@ void mbedtls_ctr_drbg_free(mbedtls_ctr_drbg_context *ctx)
mbedtls_mutex_free(&ctx->mutex);
}
#endif
-#if defined(MBEDTLS_AES_C)
- mbedtls_aes_free(&ctx->aes_ctx);
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
ctr_drbg_destroy_psa_contex(&ctx->psa_ctx);
+#else
+ mbedtls_aes_free(&ctx->aes_ctx);
#endif
mbedtls_platform_zeroize(ctx, sizeof(mbedtls_ctr_drbg_context));
ctx->reseed_interval = MBEDTLS_CTR_DRBG_RESEED_INTERVAL;
@@ -168,15 +168,15 @@ static int block_cipher_df(unsigned char *output,
unsigned char chain[MBEDTLS_CTR_DRBG_BLOCKSIZE];
unsigned char *p, *iv;
int ret = 0;
-#if defined(MBEDTLS_AES_C)
- mbedtls_aes_context aes_ctx;
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
psa_status_t status;
size_t tmp_len;
mbedtls_ctr_drbg_psa_context psa_ctx;
psa_ctx.key_id = MBEDTLS_SVC_KEY_ID_INIT;
psa_ctx.operation = psa_cipher_operation_init();
+#else
+ mbedtls_aes_context aes_ctx;
#endif
int i, j;
@@ -209,19 +209,19 @@ static int block_cipher_df(unsigned char *output,
key[i] = i;
}
-#if defined(MBEDTLS_AES_C)
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
+ status = ctr_drbg_setup_psa_context(&psa_ctx, key, sizeof(key));
+ if (status != PSA_SUCCESS) {
+ ret = psa_generic_status_to_mbedtls(status);
+ goto exit;
+ }
+#else
mbedtls_aes_init(&aes_ctx);
if ((ret = mbedtls_aes_setkey_enc(&aes_ctx, key,
MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
goto exit;
}
-#else
- status = ctr_drbg_setup_psa_context(&psa_ctx, key, sizeof(key));
- if (status != PSA_SUCCESS) {
- ret = psa_generic_status_to_mbedtls(status);
- goto exit;
- }
#endif
/*
@@ -238,18 +238,18 @@ static int block_cipher_df(unsigned char *output,
use_len -= (use_len >= MBEDTLS_CTR_DRBG_BLOCKSIZE) ?
MBEDTLS_CTR_DRBG_BLOCKSIZE : use_len;
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT,
- chain, chain)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
status = psa_cipher_update(&psa_ctx.operation, chain, MBEDTLS_CTR_DRBG_BLOCKSIZE,
chain, MBEDTLS_CTR_DRBG_BLOCKSIZE, &tmp_len);
if (status != PSA_SUCCESS) {
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT,
+ chain, chain)) != 0) {
+ goto exit;
+ }
#endif
}
@@ -264,12 +264,7 @@ static int block_cipher_df(unsigned char *output,
/*
* Do final encryption with reduced data
*/
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_setkey_enc(&aes_ctx, tmp,
- MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
ctr_drbg_destroy_psa_contex(&psa_ctx);
status = ctr_drbg_setup_psa_context(&psa_ctx, tmp, MBEDTLS_CTR_DRBG_KEYSIZE);
@@ -277,32 +272,37 @@ static int block_cipher_df(unsigned char *output,
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_setkey_enc(&aes_ctx, tmp,
+ MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
+ goto exit;
+ }
#endif
iv = tmp + MBEDTLS_CTR_DRBG_KEYSIZE;
p = output;
for (j = 0; j < MBEDTLS_CTR_DRBG_SEEDLEN; j += MBEDTLS_CTR_DRBG_BLOCKSIZE) {
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT,
- iv, iv)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
status = psa_cipher_update(&psa_ctx.operation, iv, MBEDTLS_CTR_DRBG_BLOCKSIZE,
iv, MBEDTLS_CTR_DRBG_BLOCKSIZE, &tmp_len);
if (status != PSA_SUCCESS) {
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT,
+ iv, iv)) != 0) {
+ goto exit;
+ }
#endif
memcpy(p, iv, MBEDTLS_CTR_DRBG_BLOCKSIZE);
p += MBEDTLS_CTR_DRBG_BLOCKSIZE;
}
exit:
-#if defined(MBEDTLS_AES_C)
- mbedtls_aes_free(&aes_ctx);
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
ctr_drbg_destroy_psa_contex(&psa_ctx);
+#else
+ mbedtls_aes_free(&aes_ctx);
#endif
/*
* tidy up the stack
@@ -336,7 +336,7 @@ static int ctr_drbg_update_internal(mbedtls_ctr_drbg_context *ctx,
unsigned char *p = tmp;
int j;
int ret = 0;
-#if !defined(MBEDTLS_AES_C)
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
psa_status_t status;
size_t tmp_len;
#endif
@@ -352,18 +352,18 @@ static int ctr_drbg_update_internal(mbedtls_ctr_drbg_context *ctx,
/*
* Crypt counter block
*/
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT,
- ctx->counter, p)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
status = psa_cipher_update(&ctx->psa_ctx.operation, ctx->counter, sizeof(ctx->counter),
p, MBEDTLS_CTR_DRBG_BLOCKSIZE, &tmp_len);
if (status != PSA_SUCCESS) {
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT,
+ ctx->counter, p)) != 0) {
+ goto exit;
+ }
#endif
p += MBEDTLS_CTR_DRBG_BLOCKSIZE;
@@ -374,12 +374,7 @@ static int ctr_drbg_update_internal(mbedtls_ctr_drbg_context *ctx,
/*
* Update key and counter
*/
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_setkey_enc(&ctx->aes_ctx, tmp,
- MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
ctr_drbg_destroy_psa_contex(&ctx->psa_ctx);
status = ctr_drbg_setup_psa_context(&ctx->psa_ctx, tmp, MBEDTLS_CTR_DRBG_KEYSIZE);
@@ -387,6 +382,11 @@ static int ctr_drbg_update_internal(mbedtls_ctr_drbg_context *ctx,
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_setkey_enc(&ctx->aes_ctx, tmp,
+ MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
+ goto exit;
+ }
#endif
memcpy(ctx->counter, tmp + MBEDTLS_CTR_DRBG_KEYSIZE,
MBEDTLS_CTR_DRBG_BLOCKSIZE);
@@ -564,12 +564,7 @@ int mbedtls_ctr_drbg_seed(mbedtls_ctr_drbg_context *ctx,
good_nonce_len(ctx->entropy_len));
/* Initialize with an empty key. */
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_setkey_enc(&ctx->aes_ctx, key,
- MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
- return ret;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
psa_status_t status;
status = ctr_drbg_setup_psa_context(&ctx->psa_ctx, key, MBEDTLS_CTR_DRBG_KEYSIZE);
@@ -577,6 +572,11 @@ int mbedtls_ctr_drbg_seed(mbedtls_ctr_drbg_context *ctx,
ret = psa_generic_status_to_mbedtls(status);
return status;
}
+#else
+ if ((ret = mbedtls_aes_setkey_enc(&ctx->aes_ctx, key,
+ MBEDTLS_CTR_DRBG_KEYBITS)) != 0) {
+ return ret;
+ }
#endif
/* Do the initial seeding. */
@@ -655,12 +655,7 @@ int mbedtls_ctr_drbg_random_with_add(void *p_rng,
/*
* Crypt counter block
*/
-#if defined(MBEDTLS_AES_C)
- if ((ret = mbedtls_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT,
- ctx->counter, locals.tmp)) != 0) {
- goto exit;
- }
-#else
+#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO)
psa_status_t status;
size_t tmp_len;
@@ -670,6 +665,11 @@ int mbedtls_ctr_drbg_random_with_add(void *p_rng,
ret = psa_generic_status_to_mbedtls(status);
goto exit;
}
+#else
+ if ((ret = mbedtls_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT,
+ ctx->counter, locals.tmp)) != 0) {
+ goto exit;
+ }
#endif
use_len = (output_len > MBEDTLS_CTR_DRBG_BLOCKSIZE)
diff --git a/thirdparty/mbedtls/library/entropy.c b/thirdparty/mbedtls/library/entropy.c
index e3bc8516e2..7dcf067a52 100644
--- a/thirdparty/mbedtls/library/entropy.c
+++ b/thirdparty/mbedtls/library/entropy.c
@@ -61,6 +61,10 @@ void mbedtls_entropy_init(mbedtls_entropy_context *ctx)
void mbedtls_entropy_free(mbedtls_entropy_context *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
/* If the context was already free, don't call free() again.
* This is important for mutexes which don't allow double-free. */
if (ctx->accumulator_started == -1) {
diff --git a/thirdparty/mbedtls/library/entropy_poll.c b/thirdparty/mbedtls/library/entropy_poll.c
index 794ee03a83..611768cd85 100644
--- a/thirdparty/mbedtls/library/entropy_poll.c
+++ b/thirdparty/mbedtls/library/entropy_poll.c
@@ -5,10 +5,12 @@
* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
*/
-#if defined(__linux__) || defined(__midipix__) && !defined(_GNU_SOURCE)
+#if defined(__linux__) || defined(__midipix__)
/* Ensure that syscall() is available even when compiling with -std=c99 */
+#if !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif
+#endif
#include "common.h"
diff --git a/thirdparty/mbedtls/library/error.c b/thirdparty/mbedtls/library/error.c
index 84b637aeb2..6ad7162ab5 100644
--- a/thirdparty/mbedtls/library/error.c
+++ b/thirdparty/mbedtls/library/error.c
@@ -418,7 +418,7 @@ const char *mbedtls_high_level_strerr(int error_code)
case -(MBEDTLS_ERR_SSL_BAD_CERTIFICATE):
return( "SSL - Processing of the Certificate handshake message failed" );
case -(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET):
- return( "SSL - * Received NewSessionTicket Post Handshake Message. This error code is experimental and may be changed or removed without notice" );
+ return( "SSL - A TLS 1.3 NewSessionTicket message has been received" );
case -(MBEDTLS_ERR_SSL_CANNOT_READ_EARLY_DATA):
return( "SSL - Not possible to read early data" );
case -(MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA):
diff --git a/thirdparty/mbedtls/library/lmots.c b/thirdparty/mbedtls/library/lmots.c
index c7091b49e1..c51cb41ece 100644
--- a/thirdparty/mbedtls/library/lmots.c
+++ b/thirdparty/mbedtls/library/lmots.c
@@ -387,6 +387,10 @@ void mbedtls_lmots_public_init(mbedtls_lmots_public_t *ctx)
void mbedtls_lmots_public_free(mbedtls_lmots_public_t *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_platform_zeroize(ctx, sizeof(*ctx));
}
@@ -556,6 +560,10 @@ void mbedtls_lmots_private_init(mbedtls_lmots_private_t *ctx)
void mbedtls_lmots_private_free(mbedtls_lmots_private_t *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_platform_zeroize(ctx,
sizeof(*ctx));
}
diff --git a/thirdparty/mbedtls/library/lms.c b/thirdparty/mbedtls/library/lms.c
index 8d3cae0524..7f7bec068b 100644
--- a/thirdparty/mbedtls/library/lms.c
+++ b/thirdparty/mbedtls/library/lms.c
@@ -229,6 +229,10 @@ void mbedtls_lms_public_init(mbedtls_lms_public_t *ctx)
void mbedtls_lms_public_free(mbedtls_lms_public_t *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_platform_zeroize(ctx, sizeof(*ctx));
}
@@ -528,6 +532,10 @@ void mbedtls_lms_private_init(mbedtls_lms_private_t *ctx)
void mbedtls_lms_private_free(mbedtls_lms_private_t *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
unsigned int idx;
if (ctx->have_private_key) {
diff --git a/thirdparty/mbedtls/library/md.c b/thirdparty/mbedtls/library/md.c
index 12a3ea2374..c95846aa04 100644
--- a/thirdparty/mbedtls/library/md.c
+++ b/thirdparty/mbedtls/library/md.c
@@ -41,7 +41,7 @@
#include "mbedtls/sha512.h"
#include "mbedtls/sha3.h"
-#if defined(MBEDTLS_PSA_CRYPTO_C)
+#if defined(MBEDTLS_PSA_CRYPTO_CLIENT)
#include <psa/crypto.h>
#include "md_psa.h"
#include "psa_util_internal.h"
@@ -761,13 +761,13 @@ mbedtls_md_type_t mbedtls_md_get_type(const mbedtls_md_info_t *md_info)
return md_info->type;
}
-#if defined(MBEDTLS_PSA_CRYPTO_C)
+#if defined(MBEDTLS_PSA_CRYPTO_CLIENT)
int mbedtls_md_error_from_psa(psa_status_t status)
{
return PSA_TO_MBEDTLS_ERR_LIST(status, psa_to_md_errors,
psa_generic_status_to_mbedtls);
}
-#endif /* MBEDTLS_PSA_CRYPTO_C */
+#endif /* MBEDTLS_PSA_CRYPTO_CLIENT */
/************************************************************************
diff --git a/thirdparty/mbedtls/library/net_sockets.c b/thirdparty/mbedtls/library/net_sockets.c
index edec5876ad..ef89a88ef0 100644
--- a/thirdparty/mbedtls/library/net_sockets.c
+++ b/thirdparty/mbedtls/library/net_sockets.c
@@ -683,7 +683,7 @@ void mbedtls_net_close(mbedtls_net_context *ctx)
*/
void mbedtls_net_free(mbedtls_net_context *ctx)
{
- if (ctx->fd == -1) {
+ if (ctx == NULL || ctx->fd == -1) {
return;
}
diff --git a/thirdparty/mbedtls/library/nist_kw.c b/thirdparty/mbedtls/library/nist_kw.c
index f15425b8bd..8faafe43f1 100644
--- a/thirdparty/mbedtls/library/nist_kw.c
+++ b/thirdparty/mbedtls/library/nist_kw.c
@@ -102,6 +102,10 @@ int mbedtls_nist_kw_setkey(mbedtls_nist_kw_context *ctx,
*/
void mbedtls_nist_kw_free(mbedtls_nist_kw_context *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_cipher_free(&ctx->cipher_ctx);
mbedtls_platform_zeroize(ctx, sizeof(mbedtls_nist_kw_context));
}
diff --git a/thirdparty/mbedtls/library/pem.c b/thirdparty/mbedtls/library/pem.c
index 0fee5df43a..0207601456 100644
--- a/thirdparty/mbedtls/library/pem.c
+++ b/thirdparty/mbedtls/library/pem.c
@@ -481,6 +481,10 @@ int mbedtls_pem_read_buffer(mbedtls_pem_context *ctx, const char *header, const
void mbedtls_pem_free(mbedtls_pem_context *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
if (ctx->buf != NULL) {
mbedtls_zeroize_and_free(ctx->buf, ctx->buflen);
}
diff --git a/thirdparty/mbedtls/library/pk.c b/thirdparty/mbedtls/library/pk.c
index 097777f2c0..3fe51ea34f 100644
--- a/thirdparty/mbedtls/library/pk.c
+++ b/thirdparty/mbedtls/library/pk.c
@@ -868,7 +868,6 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id,
psa_status_t status;
psa_key_attributes_t key_attr = PSA_KEY_ATTRIBUTES_INIT;
psa_key_type_t key_type;
- psa_algorithm_t alg_type;
size_t key_bits;
/* Use a buffer size large enough to contain either a key pair or public key. */
unsigned char exp_key[PSA_EXPORT_KEY_PAIR_OR_PUBLIC_MAX_SIZE];
@@ -899,7 +898,6 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id,
key_type = PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR(key_type);
}
key_bits = psa_get_key_bits(&key_attr);
- alg_type = psa_get_key_algorithm(&key_attr);
#if defined(MBEDTLS_RSA_C)
if ((key_type == PSA_KEY_TYPE_RSA_KEY_PAIR) ||
@@ -919,6 +917,7 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id,
goto exit;
}
+ psa_algorithm_t alg_type = psa_get_key_algorithm(&key_attr);
mbedtls_md_type_t md_type = MBEDTLS_MD_NONE;
if (PSA_ALG_GET_HASH(alg_type) != PSA_ALG_ANY_HASH) {
md_type = mbedtls_md_type_from_psa_alg(alg_type);
@@ -968,6 +967,7 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id,
} else
#endif /* MBEDTLS_PK_HAVE_ECC_KEYS */
{
+ (void) key_bits;
return MBEDTLS_ERR_PK_BAD_INPUT_DATA;
}
@@ -1327,43 +1327,19 @@ int mbedtls_pk_sign_ext(mbedtls_pk_type_t pk_type,
}
if (mbedtls_pk_get_type(ctx) == MBEDTLS_PK_OPAQUE) {
- psa_key_attributes_t key_attr = PSA_KEY_ATTRIBUTES_INIT;
- psa_algorithm_t psa_alg, sign_alg;
-#if defined(MBEDTLS_PSA_CRYPTO_C)
- psa_algorithm_t psa_enrollment_alg;
-#endif /* MBEDTLS_PSA_CRYPTO_C */
psa_status_t status;
- status = psa_get_key_attributes(ctx->priv_id, &key_attr);
- if (status != PSA_SUCCESS) {
- return PSA_PK_RSA_TO_MBEDTLS_ERR(status);
- }
- psa_alg = psa_get_key_algorithm(&key_attr);
-#if defined(MBEDTLS_PSA_CRYPTO_C)
- psa_enrollment_alg = psa_get_key_enrollment_algorithm(&key_attr);
-#endif /* MBEDTLS_PSA_CRYPTO_C */
- psa_reset_key_attributes(&key_attr);
-
- /* Since we're PK type is MBEDTLS_PK_RSASSA_PSS at least one between
- * alg and enrollment alg should be of type RSA_PSS. */
- if (PSA_ALG_IS_RSA_PSS(psa_alg)) {
- sign_alg = psa_alg;
- }
-#if defined(MBEDTLS_PSA_CRYPTO_C)
- else if (PSA_ALG_IS_RSA_PSS(psa_enrollment_alg)) {
- sign_alg = psa_enrollment_alg;
- }
-#endif /* MBEDTLS_PSA_CRYPTO_C */
- else {
- /* The opaque key has no RSA PSS algorithm associated. */
- return MBEDTLS_ERR_PK_BAD_INPUT_DATA;
- }
- /* Adjust the hashing algorithm. */
- sign_alg = (sign_alg & ~PSA_ALG_HASH_MASK) | PSA_ALG_GET_HASH(psa_md_alg);
-
- status = psa_sign_hash(ctx->priv_id, sign_alg,
+ /* PSA_ALG_RSA_PSS() behaves the same as PSA_ALG_RSA_PSS_ANY_SALT() when
+ * performing a signature, but they are encoded differently. Instead of
+ * extracting the proper one from the wrapped key policy, just try both. */
+ status = psa_sign_hash(ctx->priv_id, PSA_ALG_RSA_PSS(psa_md_alg),
hash, hash_len,
sig, sig_size, sig_len);
+ if (status == PSA_ERROR_NOT_PERMITTED) {
+ status = psa_sign_hash(ctx->priv_id, PSA_ALG_RSA_PSS_ANY_SALT(psa_md_alg),
+ hash, hash_len,
+ sig, sig_size, sig_len);
+ }
return PSA_PK_RSA_TO_MBEDTLS_ERR(status);
}
diff --git a/thirdparty/mbedtls/library/platform_util.c b/thirdparty/mbedtls/library/platform_util.c
index 0741bf575e..19ef07aead 100644
--- a/thirdparty/mbedtls/library/platform_util.c
+++ b/thirdparty/mbedtls/library/platform_util.c
@@ -149,7 +149,7 @@ void mbedtls_zeroize_and_free(void *buf, size_t len)
#include <time.h>
#if !defined(_WIN32) && (defined(unix) || \
defined(__unix) || defined(__unix__) || (defined(__APPLE__) && \
- defined(__MACH__)) || defined__midipix__)
+ defined(__MACH__)) || defined(__midipix__))
#include <unistd.h>
#endif /* !_WIN32 && (unix || __unix || __unix__ ||
* (__APPLE__ && __MACH__) || __midipix__) */
diff --git a/thirdparty/mbedtls/library/psa_crypto_core.h b/thirdparty/mbedtls/library/psa_crypto_core.h
index c059162efe..21e7559f01 100644
--- a/thirdparty/mbedtls/library/psa_crypto_core.h
+++ b/thirdparty/mbedtls/library/psa_crypto_core.h
@@ -59,6 +59,8 @@ typedef enum {
* and metadata for one key.
*/
typedef struct {
+ /* This field is accessed in a lot of places. Putting it first
+ * reduces the code size. */
psa_key_attributes_t attr;
/*
@@ -78,35 +80,77 @@ typedef struct {
* slots that are in a suitable state for the function.
* For example, psa_get_and_lock_key_slot_in_memory, which finds a slot
* containing a given key ID, will only check slots whose state variable is
- * PSA_SLOT_FULL. */
+ * PSA_SLOT_FULL.
+ */
psa_key_slot_state_t state;
- /*
- * Number of functions registered as reading the material in the key slot.
- *
- * Library functions must not write directly to registered_readers
- *
- * A function must call psa_register_read(slot) before reading the current
- * contents of the slot for an operation.
- * They then must call psa_unregister_read(slot) once they have finished
- * reading the current contents of the slot. If the key slot mutex is not
- * held (when mutexes are enabled), this call must be done via a call to
- * psa_unregister_read_under_mutex(slot).
- * A function must call psa_key_slot_has_readers(slot) to check if
- * the slot is in use for reading.
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+ /* The index of the slice containing this slot.
+ * This field must be filled if the slot contains a key
+ * (including keys being created or destroyed), and can be either
+ * filled or 0 when the slot is free.
*
- * This counter is used to prevent resetting the key slot while the library
- * may access it. For example, such control is needed in the following
- * scenarios:
- * . In case of key slot starvation, all key slots contain the description
- * of a key, and the library asks for the description of a persistent
- * key not present in the key slots, the key slots currently accessed by
- * the library cannot be reclaimed to free a key slot to load the
- * persistent key.
- * . In case of a multi-threaded application where one thread asks to close
- * or purge or destroy a key while it is in use by the library through
- * another thread. */
- size_t registered_readers;
+ * In most cases, the slice index can be deduced from the key identifer.
+ * We keep it in a separate field for robustness (it reduces the chance
+ * that a coding mistake in the key store will result in accessing the
+ * wrong slice), and also so that it's available even on code paths
+ * during creation or destruction where the key identifier might not be
+ * filled in.
+ * */
+ uint8_t slice_index;
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
+ union {
+ struct {
+ /* The index of the next slot in the free list for this
+ * slice, relative * to the next array element.
+ *
+ * That is, 0 means the next slot, 1 means the next slot
+ * but one, etc. -1 would mean the slot itself. -2 means
+ * the previous slot, etc.
+ *
+ * If this is beyond the array length, the free list ends with the
+ * current element.
+ *
+ * The reason for this strange encoding is that 0 means the next
+ * element. This way, when we allocate a slice and initialize it
+ * to all-zero, the slice is ready for use, with a free list that
+ * consists of all the slots in order.
+ */
+ int32_t next_free_relative_to_next;
+ } free;
+
+ struct {
+ /*
+ * Number of functions registered as reading the material in the key slot.
+ *
+ * Library functions must not write directly to registered_readers
+ *
+ * A function must call psa_register_read(slot) before reading
+ * the current contents of the slot for an operation.
+ * They then must call psa_unregister_read(slot) once they have
+ * finished reading the current contents of the slot. If the key
+ * slot mutex is not held (when mutexes are enabled), this call
+ * must be done via a call to
+ * psa_unregister_read_under_mutex(slot).
+ * A function must call psa_key_slot_has_readers(slot) to check if
+ * the slot is in use for reading.
+ *
+ * This counter is used to prevent resetting the key slot while
+ * the library may access it. For example, such control is needed
+ * in the following scenarios:
+ * . In case of key slot starvation, all key slots contain the
+ * description of a key, and the library asks for the
+ * description of a persistent key not present in the
+ * key slots, the key slots currently accessed by the
+ * library cannot be reclaimed to free a key slot to load
+ * the persistent key.
+ * . In case of a multi-threaded application where one thread
+ * asks to close or purge or destroy a key while it is in use
+ * by the library through another thread. */
+ size_t registered_readers;
+ } occupied;
+ } var;
/* Dynamically allocated key data buffer.
* Format as specified in psa_export_key(). */
@@ -169,7 +213,7 @@ typedef struct {
*/
static inline int psa_key_slot_has_readers(const psa_key_slot_t *slot)
{
- return slot->registered_readers > 0;
+ return slot->var.occupied.registered_readers > 0;
}
#if defined(MBEDTLS_PSA_CRYPTO_SE_C)
@@ -343,19 +387,18 @@ psa_status_t psa_export_public_key_internal(
const uint8_t *key_buffer, size_t key_buffer_size,
uint8_t *data, size_t data_size, size_t *data_length);
-/** Whether a key production parameters structure is the default.
+/** Whether a key custom production parameters structure is the default.
*
- * Calls to a key generation driver with non-default production parameters
+ * Calls to a key generation driver with non-default custom production parameters
* require a driver supporting custom production parameters.
*
- * \param[in] params The key production parameters to check.
- * \param params_data_length Size of `params->data` in bytes.
+ * \param[in] custom The key custom production parameters to check.
+ * \param custom_data_length Size of the associated variable-length data
+ * in bytes.
*/
-#ifndef __cplusplus
-int psa_key_production_parameters_are_default(
- const psa_key_production_parameters_t *params,
- size_t params_data_length);
-#endif
+int psa_custom_key_parameters_are_default(
+ const psa_custom_key_parameters_t *custom,
+ size_t custom_data_length);
/**
* \brief Generate a key.
@@ -364,9 +407,9 @@ int psa_key_production_parameters_are_default(
* entry point.
*
* \param[in] attributes The attributes for the key to generate.
- * \param[in] params The production parameters from
- * psa_generate_key_ext().
- * \param params_data_length The size of `params->data` in bytes.
+ * \param[in] custom Custom parameters for the key generation.
+ * \param[in] custom_data Variable-length data associated with \c custom.
+ * \param custom_data_length Length of `custom_data` in bytes.
* \param[out] key_buffer Buffer where the key data is to be written.
* \param[in] key_buffer_size Size of \p key_buffer in bytes.
* \param[out] key_buffer_length On success, the number of bytes written in
@@ -380,14 +423,13 @@ int psa_key_production_parameters_are_default(
* \retval #PSA_ERROR_BUFFER_TOO_SMALL
* The size of \p key_buffer is too small.
*/
-#ifndef __cplusplus
psa_status_t psa_generate_key_internal(const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params,
- size_t params_data_length,
+ const psa_custom_key_parameters_t *custom,
+ const uint8_t *custom_data,
+ size_t custom_data_length,
uint8_t *key_buffer,
size_t key_buffer_size,
size_t *key_buffer_length);
-#endif
/** Sign a message with a private key. For hash-and-sign algorithms,
* this includes the hashing step.
diff --git a/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h b/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
index 6919971aca..b901557208 100644
--- a/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
+++ b/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
@@ -728,10 +728,10 @@ static inline psa_status_t psa_driver_wrapper_get_key_buffer_size_from_key_data(
}
}
-#ifndef __cplusplus
static inline psa_status_t psa_driver_wrapper_generate_key(
const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params, size_t params_data_length,
+ const psa_custom_key_parameters_t *custom,
+ const uint8_t *custom_data, size_t custom_data_length,
uint8_t *key_buffer, size_t key_buffer_size, size_t *key_buffer_length )
{
psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
@@ -740,7 +740,7 @@ static inline psa_status_t psa_driver_wrapper_generate_key(
#if defined(PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE)
int is_default_production =
- psa_key_production_parameters_are_default(params, params_data_length);
+ psa_custom_key_parameters_are_default(custom, custom_data_length);
if( location != PSA_KEY_LOCATION_LOCAL_STORAGE && !is_default_production )
{
/* We don't support passing custom production parameters
@@ -811,7 +811,7 @@ static inline psa_status_t psa_driver_wrapper_generate_key(
/* Software fallback */
status = psa_generate_key_internal(
- attributes, params, params_data_length,
+ attributes, custom, custom_data, custom_data_length,
key_buffer, key_buffer_size, key_buffer_length );
break;
@@ -833,7 +833,6 @@ static inline psa_status_t psa_driver_wrapper_generate_key(
return( status );
}
-#endif
static inline psa_status_t psa_driver_wrapper_import_key(
const psa_key_attributes_t *attributes,
diff --git a/thirdparty/mbedtls/library/psa_crypto_random_impl.h b/thirdparty/mbedtls/library/psa_crypto_random_impl.h
index 533fb2e940..5b5163111b 100644
--- a/thirdparty/mbedtls/library/psa_crypto_random_impl.h
+++ b/thirdparty/mbedtls/library/psa_crypto_random_impl.h
@@ -21,13 +21,10 @@ typedef mbedtls_psa_external_random_context_t mbedtls_psa_random_context_t;
#include "mbedtls/entropy.h"
/* Choose a DRBG based on configuration and availability */
-#if defined(MBEDTLS_PSA_HMAC_DRBG_MD_TYPE)
-
-#include "mbedtls/hmac_drbg.h"
-
-#elif defined(MBEDTLS_CTR_DRBG_C)
+#if defined(MBEDTLS_CTR_DRBG_C)
#include "mbedtls/ctr_drbg.h"
+#undef MBEDTLS_PSA_HMAC_DRBG_MD_TYPE
#elif defined(MBEDTLS_HMAC_DRBG_C)
@@ -49,17 +46,11 @@ typedef mbedtls_psa_external_random_context_t mbedtls_psa_random_context_t;
#error "No hash algorithm available for HMAC_DBRG."
#endif
-#else /* !MBEDTLS_PSA_HMAC_DRBG_MD_TYPE && !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C*/
+#else /* !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C*/
#error "No DRBG module available for the psa_crypto module."
-#endif /* !MBEDTLS_PSA_HMAC_DRBG_MD_TYPE && !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C*/
-
-#if defined(MBEDTLS_CTR_DRBG_C)
-#include "mbedtls/ctr_drbg.h"
-#elif defined(MBEDTLS_HMAC_DRBG_C)
-#include "mbedtls/hmac_drbg.h"
-#endif /* !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C */
+#endif /* !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C*/
/* The maximum number of bytes that mbedtls_psa_get_random() is expected to return. */
#if defined(MBEDTLS_CTR_DRBG_C)
diff --git a/thirdparty/mbedtls/library/psa_crypto_rsa.h b/thirdparty/mbedtls/library/psa_crypto_rsa.h
index 6d695ddf50..1a780006a9 100644
--- a/thirdparty/mbedtls/library/psa_crypto_rsa.h
+++ b/thirdparty/mbedtls/library/psa_crypto_rsa.h
@@ -105,17 +105,11 @@ psa_status_t mbedtls_psa_rsa_export_public_key(
/**
* \brief Generate an RSA key.
*
- * \note The signature of the function is that of a PSA driver generate_key
- * entry point.
- *
* \param[in] attributes The attributes for the RSA key to generate.
- * \param[in] params Production parameters for the key
- * generation. This function only uses
- * `params->data`,
- * which contains the public exponent.
+ * \param[in] custom_data The public exponent to use.
* This can be a null pointer if
* \c params_data_length is 0.
- * \param params_data_length Length of `params->data` in bytes.
+ * \param custom_data_length Length of \p custom_data in bytes.
* This can be 0, in which case the
* public exponent will be 65537.
* \param[out] key_buffer Buffer where the key data is to be written.
@@ -130,12 +124,10 @@ psa_status_t mbedtls_psa_rsa_export_public_key(
* \retval #PSA_ERROR_BUFFER_TOO_SMALL
* The size of \p key_buffer is too small.
*/
-#ifndef __cplusplus
psa_status_t mbedtls_psa_rsa_generate_key(
const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params, size_t params_data_length,
+ const uint8_t *custom_data, size_t custom_data_length,
uint8_t *key_buffer, size_t key_buffer_size, size_t *key_buffer_length);
-#endif
/** Sign an already-calculated hash with an RSA private key.
*
diff --git a/thirdparty/mbedtls/library/psa_crypto_slot_management.h b/thirdparty/mbedtls/library/psa_crypto_slot_management.h
index bcfc9d8adc..af1208e3ae 100644
--- a/thirdparty/mbedtls/library/psa_crypto_slot_management.h
+++ b/thirdparty/mbedtls/library/psa_crypto_slot_management.h
@@ -15,20 +15,26 @@
/** Range of volatile key identifiers.
*
- * The last #MBEDTLS_PSA_KEY_SLOT_COUNT identifiers of the implementation
+ * The first #MBEDTLS_PSA_KEY_SLOT_COUNT identifiers of the implementation
* range of key identifiers are reserved for volatile key identifiers.
- * A volatile key identifier is equal to #PSA_KEY_ID_VOLATILE_MIN plus the
- * index of the key slot containing the volatile key definition.
+ *
+ * If \c id is a a volatile key identifier, #PSA_KEY_ID_VOLATILE_MIN - \c id
+ * indicates the key slot containing the volatile key definition. See
+ * psa_crypto_slot_management.c for details.
*/
/** The minimum value for a volatile key identifier.
*/
-#define PSA_KEY_ID_VOLATILE_MIN (PSA_KEY_ID_VENDOR_MAX - \
- MBEDTLS_PSA_KEY_SLOT_COUNT + 1)
+#define PSA_KEY_ID_VOLATILE_MIN PSA_KEY_ID_VENDOR_MIN
/** The maximum value for a volatile key identifier.
*/
-#define PSA_KEY_ID_VOLATILE_MAX PSA_KEY_ID_VENDOR_MAX
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+#define PSA_KEY_ID_VOLATILE_MAX (MBEDTLS_PSA_KEY_ID_BUILTIN_MIN - 1)
+#else /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+#define PSA_KEY_ID_VOLATILE_MAX \
+ (PSA_KEY_ID_VOLATILE_MIN + MBEDTLS_PSA_KEY_SLOT_COUNT - 1)
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
/** Test whether a key identifier is a volatile key identifier.
*
@@ -58,6 +64,9 @@ static inline int psa_key_id_is_volatile(psa_key_id_t key_id)
* It is the responsibility of the caller to call psa_unregister_read(slot)
* when they have finished reading the contents of the slot.
*
+ * On failure, `*p_slot` is set to NULL. This ensures that it is always valid
+ * to call psa_unregister_read on the returned slot.
+ *
* \param key Key identifier to query.
* \param[out] p_slot On success, `*p_slot` contains a pointer to the
* key slot containing the description of the key
@@ -91,6 +100,24 @@ psa_status_t psa_get_and_lock_key_slot(mbedtls_svc_key_id_t key,
*/
psa_status_t psa_initialize_key_slots(void);
+#if defined(MBEDTLS_TEST_HOOKS) && defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+/* Allow test code to customize the key slice length. We use this in tests
+ * that exhaust the key store to reach a full key store in reasonable time
+ * and memory.
+ *
+ * The length of each slice must be between 1 and
+ * (1 << KEY_ID_SLOT_INDEX_WIDTH) inclusive.
+ *
+ * The length for a given slice index must not change while
+ * the key store is initialized.
+ */
+extern size_t (*mbedtls_test_hook_psa_volatile_key_slice_length)(
+ size_t slice_idx);
+
+/* The number of volatile key slices. */
+size_t psa_key_slot_volatile_slice_count(void);
+#endif
+
/** Delete all data from key slots in memory.
* This function is not thread safe, it wipes every key slot regardless of
* state and reader count. It should only be called when no slot is in use.
@@ -110,13 +137,22 @@ void psa_wipe_all_key_slots(void);
* If multi-threading is enabled, the caller must hold the
* global key slot mutex.
*
- * \param[out] volatile_key_id On success, volatile key identifier
- * associated to the returned slot.
+ * \param[out] volatile_key_id - If null, reserve a cache slot for
+ * a persistent or built-in key.
+ * - If non-null, allocate a slot for
+ * a volatile key. On success,
+ * \p *volatile_key_id is the
+ * identifier corresponding to the
+ * returned slot. It is the caller's
+ * responsibility to set this key identifier
+ * in the attributes.
* \param[out] p_slot On success, a pointer to the slot.
*
* \retval #PSA_SUCCESS \emptydescription
* \retval #PSA_ERROR_INSUFFICIENT_MEMORY
* There were no free key slots.
+ * When #MBEDTLS_PSA_KEY_STORE_DYNAMIC is enabled, there was not
+ * enough memory to allocate more slots.
* \retval #PSA_ERROR_BAD_STATE \emptydescription
* \retval #PSA_ERROR_CORRUPTION_DETECTED
* This function attempted to operate on a key slot which was in an
@@ -125,6 +161,29 @@ void psa_wipe_all_key_slots(void);
psa_status_t psa_reserve_free_key_slot(psa_key_id_t *volatile_key_id,
psa_key_slot_t **p_slot);
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+/** Return a key slot to the free list.
+ *
+ * Call this function when a slot obtained from psa_reserve_free_key_slot()
+ * is no longer in use.
+ *
+ * If multi-threading is enabled, the caller must hold the
+ * global key slot mutex.
+ *
+ * \param slice_idx The slice containing the slot.
+ * This is `slot->slice_index` when the slot
+ * is obtained from psa_reserve_free_key_slot().
+ * \param slot The key slot.
+ *
+ * \retval #PSA_SUCCESS \emptydescription
+ * \retval #PSA_ERROR_CORRUPTION_DETECTED
+ * This function attempted to operate on a key slot which was in an
+ * unexpected state.
+ */
+psa_status_t psa_free_key_slot(size_t slice_idx,
+ psa_key_slot_t *slot);
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
/** Change the state of a key slot.
*
* This function changes the state of the key slot from expected_state to
@@ -171,10 +230,10 @@ static inline psa_status_t psa_key_slot_state_transition(
static inline psa_status_t psa_register_read(psa_key_slot_t *slot)
{
if ((slot->state != PSA_SLOT_FULL) ||
- (slot->registered_readers >= SIZE_MAX)) {
+ (slot->var.occupied.registered_readers >= SIZE_MAX)) {
return PSA_ERROR_CORRUPTION_DETECTED;
}
- slot->registered_readers++;
+ slot->var.occupied.registered_readers++;
return PSA_SUCCESS;
}
diff --git a/thirdparty/mbedtls/library/rsa.c b/thirdparty/mbedtls/library/rsa.c
index 7eb4a259ea..557faaf363 100644
--- a/thirdparty/mbedtls/library/rsa.c
+++ b/thirdparty/mbedtls/library/rsa.c
@@ -29,6 +29,7 @@
#include "mbedtls/rsa.h"
#include "bignum_core.h"
+#include "bignum_internal.h"
#include "rsa_alt_helpers.h"
#include "rsa_internal.h"
#include "mbedtls/oid.h"
@@ -1259,7 +1260,7 @@ int mbedtls_rsa_public(mbedtls_rsa_context *ctx,
}
olen = ctx->len;
- MBEDTLS_MPI_CHK(mbedtls_mpi_exp_mod(&T, &T, &ctx->E, &ctx->N, &ctx->RN));
+ MBEDTLS_MPI_CHK(mbedtls_mpi_exp_mod_unsafe(&T, &T, &ctx->E, &ctx->N, &ctx->RN));
MBEDTLS_MPI_CHK(mbedtls_mpi_write_binary(&T, output, olen));
cleanup:
diff --git a/thirdparty/mbedtls/library/sha256.c b/thirdparty/mbedtls/library/sha256.c
index 87889817a4..159acccaeb 100644
--- a/thirdparty/mbedtls/library/sha256.c
+++ b/thirdparty/mbedtls/library/sha256.c
@@ -44,7 +44,9 @@
#endif /* defined(__clang__) && (__clang_major__ >= 4) */
/* Ensure that SIG_SETMASK is defined when -std=c99 is used. */
+#if !defined(_GNU_SOURCE)
#define _GNU_SOURCE
+#endif
#include "common.h"
@@ -150,7 +152,9 @@ static int mbedtls_a64_crypto_sha256_determine_support(void)
return 1;
}
#elif defined(MBEDTLS_PLATFORM_IS_WINDOWS_ON_ARM64)
+#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
+#endif
#include <Windows.h>
#include <processthreadsapi.h>
diff --git a/thirdparty/mbedtls/library/ssl_cookie.c b/thirdparty/mbedtls/library/ssl_cookie.c
index 2772cac4be..acc9e8c080 100644
--- a/thirdparty/mbedtls/library/ssl_cookie.c
+++ b/thirdparty/mbedtls/library/ssl_cookie.c
@@ -84,6 +84,10 @@ void mbedtls_ssl_cookie_set_timeout(mbedtls_ssl_cookie_ctx *ctx, unsigned long d
void mbedtls_ssl_cookie_free(mbedtls_ssl_cookie_ctx *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
#if defined(MBEDTLS_USE_PSA_CRYPTO)
psa_destroy_key(ctx->psa_hmac_key);
#else
diff --git a/thirdparty/mbedtls/library/ssl_debug_helpers_generated.c b/thirdparty/mbedtls/library/ssl_debug_helpers_generated.c
index f8b4448c86..734c417b8b 100644
--- a/thirdparty/mbedtls/library/ssl_debug_helpers_generated.c
+++ b/thirdparty/mbedtls/library/ssl_debug_helpers_generated.c
@@ -60,7 +60,7 @@ const char *mbedtls_ssl_named_group_to_str( uint16_t in )
return "ffdhe8192";
};
- return "UNKOWN";
+ return "UNKNOWN";
}
const char *mbedtls_ssl_sig_alg_to_str( uint16_t in )
{
diff --git a/thirdparty/mbedtls/library/ssl_misc.h b/thirdparty/mbedtls/library/ssl_misc.h
index a8807f67c6..98668798a8 100644
--- a/thirdparty/mbedtls/library/ssl_misc.h
+++ b/thirdparty/mbedtls/library/ssl_misc.h
@@ -1507,7 +1507,7 @@ int mbedtls_ssl_psk_derive_premaster(mbedtls_ssl_context *ssl,
#endif /* MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED */
#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_PSK_ENABLED)
-#if defined(MBEDTLS_SSL_CLI_C)
+#if defined(MBEDTLS_SSL_CLI_C) || defined(MBEDTLS_SSL_SRV_C)
MBEDTLS_CHECK_RETURN_CRITICAL
int mbedtls_ssl_conf_has_static_psk(mbedtls_ssl_config const *conf);
#endif
@@ -1674,18 +1674,53 @@ static inline mbedtls_x509_crt *mbedtls_ssl_own_cert(mbedtls_ssl_context *ssl)
}
/*
- * Check usage of a certificate wrt extensions:
- * keyUsage, extendedKeyUsage (later), and nSCertType (later).
+ * Verify a certificate.
+ *
+ * [in/out] ssl: misc. things read
+ * ssl->session_negotiate->verify_result updated
+ * [in] authmode: one of MBEDTLS_SSL_VERIFY_{NONE,OPTIONAL,REQUIRED}
+ * [in] chain: the certificate chain to verify (ie the peer's chain)
+ * [in] ciphersuite_info: For TLS 1.2, this session's ciphersuite;
+ * for TLS 1.3, may be left NULL.
+ * [in] rs_ctx: restart context if restartable ECC is in use;
+ * leave NULL for no restartable behaviour.
+ *
+ * Return:
+ * - 0 if the handshake should continue. Depending on the
+ * authmode it means:
+ * - REQUIRED: the certificate was found to be valid, trusted & acceptable.
+ * ssl->session_negotiate->verify_result is 0.
+ * - OPTIONAL: the certificate may or may not be acceptable, but
+ * ssl->session_negotiate->verify_result was updated with the result.
+ * - NONE: the certificate wasn't even checked.
+ * - MBEDTLS_ERR_X509_CERT_VERIFY_FAILED or MBEDTLS_ERR_SSL_BAD_CERTIFICATE if
+ * the certificate was found to be invalid/untrusted/unacceptable and the
+ * handshake should be aborted (can only happen with REQUIRED).
+ * - another error code if another error happened (out-of-memory, etc.)
+ */
+MBEDTLS_CHECK_RETURN_CRITICAL
+int mbedtls_ssl_verify_certificate(mbedtls_ssl_context *ssl,
+ int authmode,
+ mbedtls_x509_crt *chain,
+ const mbedtls_ssl_ciphersuite_t *ciphersuite_info,
+ void *rs_ctx);
+
+/*
+ * Check usage of a certificate wrt usage extensions:
+ * keyUsage and extendedKeyUsage.
+ * (Note: nSCertType is deprecated and not standard, we don't check it.)
*
- * Warning: cert_endpoint is the endpoint of the cert (ie, of our peer when we
- * check a cert we received from them)!
+ * Note: if tls_version is 1.3, ciphersuite is ignored and can be NULL.
+ *
+ * Note: recv_endpoint is the receiver's endpoint.
*
* Return 0 if everything is OK, -1 if not.
*/
MBEDTLS_CHECK_RETURN_CRITICAL
int mbedtls_ssl_check_cert_usage(const mbedtls_x509_crt *cert,
const mbedtls_ssl_ciphersuite_t *ciphersuite,
- int cert_endpoint,
+ int recv_endpoint,
+ mbedtls_ssl_protocol_version tls_version,
uint32_t *flags);
#endif /* MBEDTLS_X509_CRT_PARSE_C */
@@ -1891,6 +1926,26 @@ static inline int mbedtls_ssl_conf_is_hybrid_tls12_tls13(const mbedtls_ssl_confi
#endif /* MBEDTLS_SSL_PROTO_TLS1_2 && MBEDTLS_SSL_PROTO_TLS1_3 */
#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+
+/** \brief Initialize the PSA crypto subsystem if necessary.
+ *
+ * Call this function before doing any cryptography in a TLS 1.3 handshake.
+ *
+ * This is necessary in Mbed TLS 3.x for backward compatibility.
+ * Up to Mbed TLS 3.5, in the default configuration, you could perform
+ * a TLS connection with default parameters without having called
+ * psa_crypto_init(), since the TLS layer only supported TLS 1.2 and
+ * did not use PSA crypto. (TLS 1.2 only uses PSA crypto if
+ * MBEDTLS_USE_PSA_CRYPTO is enabled, which is not the case in the default
+ * configuration.) Starting with Mbed TLS 3.6.0, TLS 1.3 is enabled
+ * by default, and the TLS 1.3 layer uses PSA crypto. This means that
+ * applications that are not otherwise using PSA crypto and that worked
+ * with Mbed TLS 3.5 started failing in TLS 3.6.0 if they connected to
+ * a peer that supports TLS 1.3. See
+ * https://github.com/Mbed-TLS/mbedtls/issues/9072
+ */
+int mbedtls_ssl_tls13_crypto_init(mbedtls_ssl_context *ssl);
+
extern const uint8_t mbedtls_ssl_tls13_hello_retry_request_magic[
MBEDTLS_SERVER_HELLO_RANDOM_LEN];
MBEDTLS_CHECK_RETURN_CRITICAL
@@ -2914,8 +2969,37 @@ static inline void mbedtls_ssl_tls13_session_clear_ticket_flags(
{
session->ticket_flags &= ~(flags & MBEDTLS_SSL_TLS1_3_TICKET_FLAGS_MASK);
}
+
#endif /* MBEDTLS_SSL_PROTO_TLS1_3 && MBEDTLS_SSL_SESSION_TICKETS */
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
+#define MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_BIT 0
+#define MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_BIT 1
+
+#define MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_MASK \
+ (1 << MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_BIT)
+#define MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_MASK \
+ (1 << MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_BIT)
+
+static inline int mbedtls_ssl_conf_get_session_tickets(
+ const mbedtls_ssl_config *conf)
+{
+ return conf->session_tickets & MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_MASK ?
+ MBEDTLS_SSL_SESSION_TICKETS_ENABLED :
+ MBEDTLS_SSL_SESSION_TICKETS_DISABLED;
+}
+
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+static inline int mbedtls_ssl_conf_is_signal_new_session_tickets_enabled(
+ const mbedtls_ssl_config *conf)
+{
+ return conf->session_tickets & MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_MASK ?
+ MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED :
+ MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_DISABLED;
+}
+#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
+#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */
+
#if defined(MBEDTLS_SSL_CLI_C) && defined(MBEDTLS_SSL_PROTO_TLS1_3)
int mbedtls_ssl_tls13_finalize_client_hello(mbedtls_ssl_context *ssl);
#endif
diff --git a/thirdparty/mbedtls/library/ssl_msg.c b/thirdparty/mbedtls/library/ssl_msg.c
index b07cd96f1b..ef722d7bdc 100644
--- a/thirdparty/mbedtls/library/ssl_msg.c
+++ b/thirdparty/mbedtls/library/ssl_msg.c
@@ -5570,9 +5570,9 @@ static int ssl_check_ctr_renegotiate(mbedtls_ssl_context *ssl)
#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
-#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
+#if defined(MBEDTLS_SSL_CLI_C)
MBEDTLS_CHECK_RETURN_CRITICAL
-static int ssl_tls13_check_new_session_ticket(mbedtls_ssl_context *ssl)
+static int ssl_tls13_is_new_session_ticket(mbedtls_ssl_context *ssl)
{
if ((ssl->in_hslen == mbedtls_ssl_hs_hdr_len(ssl)) ||
@@ -5580,15 +5580,9 @@ static int ssl_tls13_check_new_session_ticket(mbedtls_ssl_context *ssl)
return 0;
}
- ssl->keep_current_message = 1;
-
- MBEDTLS_SSL_DEBUG_MSG(3, ("NewSessionTicket received"));
- mbedtls_ssl_handshake_set_state(ssl,
- MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET);
-
- return MBEDTLS_ERR_SSL_WANT_READ;
+ return 1;
}
-#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */
+#endif /* MBEDTLS_SSL_CLI_C */
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_handle_hs_message_post_handshake(mbedtls_ssl_context *ssl)
@@ -5596,14 +5590,29 @@ static int ssl_tls13_handle_hs_message_post_handshake(mbedtls_ssl_context *ssl)
MBEDTLS_SSL_DEBUG_MSG(3, ("received post-handshake message"));
-#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C)
+#if defined(MBEDTLS_SSL_CLI_C)
if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT) {
- int ret = ssl_tls13_check_new_session_ticket(ssl);
- if (ret != 0) {
- return ret;
+ if (ssl_tls13_is_new_session_ticket(ssl)) {
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
+ MBEDTLS_SSL_DEBUG_MSG(3, ("NewSessionTicket received"));
+ if (mbedtls_ssl_conf_is_signal_new_session_tickets_enabled(ssl->conf) ==
+ MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED) {
+ ssl->keep_current_message = 1;
+
+ mbedtls_ssl_handshake_set_state(ssl,
+ MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET);
+ return MBEDTLS_ERR_SSL_WANT_READ;
+ } else {
+ MBEDTLS_SSL_DEBUG_MSG(3, ("Ignoring NewSessionTicket, handling disabled."));
+ return 0;
+ }
+#else
+ MBEDTLS_SSL_DEBUG_MSG(3, ("Ignoring NewSessionTicket, not supported."));
+ return 0;
+#endif
}
}
-#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */
+#endif /* MBEDTLS_SSL_CLI_C */
/* Fail in all other cases. */
return MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE;
diff --git a/thirdparty/mbedtls/library/ssl_ticket.c b/thirdparty/mbedtls/library/ssl_ticket.c
index 6a31b0bee6..bfb656cf62 100644
--- a/thirdparty/mbedtls/library/ssl_ticket.c
+++ b/thirdparty/mbedtls/library/ssl_ticket.c
@@ -534,6 +534,10 @@ cleanup:
*/
void mbedtls_ssl_ticket_free(mbedtls_ssl_ticket_context *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
#if defined(MBEDTLS_USE_PSA_CRYPTO)
psa_destroy_key(ctx->keys[0].key);
psa_destroy_key(ctx->keys[1].key);
diff --git a/thirdparty/mbedtls/library/ssl_tls.c b/thirdparty/mbedtls/library/ssl_tls.c
index c5e06491c1..c773365bf6 100644
--- a/thirdparty/mbedtls/library/ssl_tls.c
+++ b/thirdparty/mbedtls/library/ssl_tls.c
@@ -132,7 +132,7 @@ int mbedtls_ssl_set_cid(mbedtls_ssl_context *ssl,
int mbedtls_ssl_get_own_cid(mbedtls_ssl_context *ssl,
int *enabled,
- unsigned char own_cid[MBEDTLS_SSL_CID_OUT_LEN_MAX],
+ unsigned char own_cid[MBEDTLS_SSL_CID_IN_LEN_MAX],
size_t *own_cid_len)
{
*enabled = MBEDTLS_SSL_CID_DISABLED;
@@ -1354,29 +1354,6 @@ static int ssl_conf_check(const mbedtls_ssl_context *ssl)
return ret;
}
-#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
- /* RFC 8446 section 4.4.3
- *
- * If the verification fails, the receiver MUST terminate the handshake with
- * a "decrypt_error" alert.
- *
- * If the client is configured as TLS 1.3 only with optional verify, return
- * bad config.
- *
- */
- if (mbedtls_ssl_conf_tls13_is_ephemeral_enabled(
- (mbedtls_ssl_context *) ssl) &&
- ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT &&
- ssl->conf->max_tls_version == MBEDTLS_SSL_VERSION_TLS1_3 &&
- ssl->conf->min_tls_version == MBEDTLS_SSL_VERSION_TLS1_3 &&
- ssl->conf->authmode == MBEDTLS_SSL_VERIFY_OPTIONAL) {
- MBEDTLS_SSL_DEBUG_MSG(
- 1, ("Optional verify auth mode "
- "is not available for TLS 1.3 client"));
- return MBEDTLS_ERR_SSL_BAD_CONFIG;
- }
-#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
-
if (ssl->conf->f_rng == NULL) {
MBEDTLS_SSL_DEBUG_MSG(1, ("no RNG provided"));
return MBEDTLS_ERR_SSL_NO_RNG;
@@ -1760,6 +1737,7 @@ int mbedtls_ssl_set_session(mbedtls_ssl_context *ssl, const mbedtls_ssl_session
#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
if (session->tls_version == MBEDTLS_SSL_VERSION_TLS1_3) {
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
const mbedtls_ssl_ciphersuite_t *ciphersuite_info =
mbedtls_ssl_ciphersuite_from_id(session->ciphersuite);
@@ -1770,6 +1748,14 @@ int mbedtls_ssl_set_session(mbedtls_ssl_context *ssl, const mbedtls_ssl_session
session->ciphersuite));
return MBEDTLS_ERR_SSL_BAD_INPUT_DATA;
}
+#else
+ /*
+ * If session tickets are not enabled, it is not possible to resume a
+ * TLS 1.3 session, thus do not make any change to the SSL context in
+ * the first place.
+ */
+ return 0;
+#endif
}
#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
@@ -2234,6 +2220,7 @@ static void ssl_remove_psk(mbedtls_ssl_context *ssl)
mbedtls_zeroize_and_free(ssl->handshake->psk,
ssl->handshake->psk_len);
ssl->handshake->psk_len = 0;
+ ssl->handshake->psk = NULL;
}
#endif /* MBEDTLS_USE_PSA_CRYPTO */
}
@@ -2999,11 +2986,24 @@ void mbedtls_ssl_conf_renegotiation_period(mbedtls_ssl_config *conf,
#if defined(MBEDTLS_SSL_SESSION_TICKETS)
#if defined(MBEDTLS_SSL_CLI_C)
+
void mbedtls_ssl_conf_session_tickets(mbedtls_ssl_config *conf, int use_tickets)
{
- conf->session_tickets = use_tickets;
+ conf->session_tickets &= ~MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_MASK;
+ conf->session_tickets |= (use_tickets != 0) <<
+ MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_BIT;
}
-#endif
+
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+void mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets(
+ mbedtls_ssl_config *conf, int signal_new_session_tickets)
+{
+ conf->session_tickets &= ~MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_MASK;
+ conf->session_tickets |= (signal_new_session_tickets != 0) <<
+ MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_BIT;
+}
+#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */
+#endif /* MBEDTLS_SSL_CLI_C */
#if defined(MBEDTLS_SSL_SRV_C)
@@ -4049,7 +4049,7 @@ static int ssl_tls13_session_save(const mbedtls_ssl_session *session,
}
static int ssl_tls13_session_load(const mbedtls_ssl_session *session,
- unsigned char *buf,
+ const unsigned char *buf,
size_t buf_len)
{
((void) session);
@@ -5868,7 +5868,33 @@ int mbedtls_ssl_config_defaults(mbedtls_ssl_config *conf,
if (endpoint == MBEDTLS_SSL_IS_CLIENT) {
conf->authmode = MBEDTLS_SSL_VERIFY_REQUIRED;
#if defined(MBEDTLS_SSL_SESSION_TICKETS)
- conf->session_tickets = MBEDTLS_SSL_SESSION_TICKETS_ENABLED;
+ mbedtls_ssl_conf_session_tickets(conf, MBEDTLS_SSL_SESSION_TICKETS_ENABLED);
+#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
+ /* Contrary to TLS 1.2 tickets, TLS 1.3 NewSessionTicket message
+ * handling is disabled by default in Mbed TLS 3.6.x for backward
+ * compatibility with client applications developed using Mbed TLS 3.5
+ * or earlier with the default configuration.
+ *
+ * Up to Mbed TLS 3.5, in the default configuration TLS 1.3 was
+ * disabled, and a Mbed TLS client with the default configuration would
+ * establish a TLS 1.2 connection with a TLS 1.2 and TLS 1.3 capable
+ * server.
+ *
+ * Starting with Mbed TLS 3.6.0, TLS 1.3 is enabled by default, and thus
+ * an Mbed TLS client with the default configuration establishes a
+ * TLS 1.3 connection with a TLS 1.2 and TLS 1.3 capable server. If
+ * following the handshake the TLS 1.3 server sends NewSessionTicket
+ * messages and the Mbed TLS client processes them, this results in
+ * Mbed TLS high level APIs (mbedtls_ssl_read(),
+ * mbedtls_ssl_handshake(), ...) to eventually return an
+ * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET non fatal error code
+ * (see the documentation of mbedtls_ssl_read() for more information on
+ * that error code). Applications unaware of that TLS 1.3 specific non
+ * fatal error code are then failing.
+ */
+ mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets(
+ conf, MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_DISABLED);
+#endif
#endif
}
#endif
@@ -6030,6 +6056,10 @@ int mbedtls_ssl_config_defaults(mbedtls_ssl_config *conf,
*/
void mbedtls_ssl_config_free(mbedtls_ssl_config *conf)
{
+ if (conf == NULL) {
+ return;
+ }
+
#if defined(MBEDTLS_DHM_C)
mbedtls_mpi_free(&conf->dhm_P);
mbedtls_mpi_free(&conf->dhm_G);
@@ -6344,71 +6374,6 @@ const char *mbedtls_ssl_get_curve_name_from_tls_id(uint16_t tls_id)
}
#endif
-#if defined(MBEDTLS_X509_CRT_PARSE_C)
-int mbedtls_ssl_check_cert_usage(const mbedtls_x509_crt *cert,
- const mbedtls_ssl_ciphersuite_t *ciphersuite,
- int cert_endpoint,
- uint32_t *flags)
-{
- int ret = 0;
- unsigned int usage = 0;
- const char *ext_oid;
- size_t ext_len;
-
- if (cert_endpoint == MBEDTLS_SSL_IS_SERVER) {
- /* Server part of the key exchange */
- switch (ciphersuite->key_exchange) {
- case MBEDTLS_KEY_EXCHANGE_RSA:
- case MBEDTLS_KEY_EXCHANGE_RSA_PSK:
- usage = MBEDTLS_X509_KU_KEY_ENCIPHERMENT;
- break;
-
- case MBEDTLS_KEY_EXCHANGE_DHE_RSA:
- case MBEDTLS_KEY_EXCHANGE_ECDHE_RSA:
- case MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA:
- usage = MBEDTLS_X509_KU_DIGITAL_SIGNATURE;
- break;
-
- case MBEDTLS_KEY_EXCHANGE_ECDH_RSA:
- case MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA:
- usage = MBEDTLS_X509_KU_KEY_AGREEMENT;
- break;
-
- /* Don't use default: we want warnings when adding new values */
- case MBEDTLS_KEY_EXCHANGE_NONE:
- case MBEDTLS_KEY_EXCHANGE_PSK:
- case MBEDTLS_KEY_EXCHANGE_DHE_PSK:
- case MBEDTLS_KEY_EXCHANGE_ECDHE_PSK:
- case MBEDTLS_KEY_EXCHANGE_ECJPAKE:
- usage = 0;
- }
- } else {
- /* Client auth: we only implement rsa_sign and mbedtls_ecdsa_sign for now */
- usage = MBEDTLS_X509_KU_DIGITAL_SIGNATURE;
- }
-
- if (mbedtls_x509_crt_check_key_usage(cert, usage) != 0) {
- *flags |= MBEDTLS_X509_BADCERT_KEY_USAGE;
- ret = -1;
- }
-
- if (cert_endpoint == MBEDTLS_SSL_IS_SERVER) {
- ext_oid = MBEDTLS_OID_SERVER_AUTH;
- ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_SERVER_AUTH);
- } else {
- ext_oid = MBEDTLS_OID_CLIENT_AUTH;
- ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_CLIENT_AUTH);
- }
-
- if (mbedtls_x509_crt_check_extended_key_usage(cert, ext_oid, ext_len) != 0) {
- *flags |= MBEDTLS_X509_BADCERT_EXT_KEY_USAGE;
- ret = -1;
- }
-
- return ret;
-}
-#endif /* MBEDTLS_X509_CRT_PARSE_C */
-
#if defined(MBEDTLS_USE_PSA_CRYPTO)
int mbedtls_ssl_get_handshake_transcript(mbedtls_ssl_context *ssl,
const mbedtls_md_type_t md,
@@ -7927,196 +7892,6 @@ static int ssl_parse_certificate_coordinate(mbedtls_ssl_context *ssl,
return SSL_CERTIFICATE_EXPECTED;
}
-MBEDTLS_CHECK_RETURN_CRITICAL
-static int ssl_parse_certificate_verify(mbedtls_ssl_context *ssl,
- int authmode,
- mbedtls_x509_crt *chain,
- void *rs_ctx)
-{
- int ret = 0;
- const mbedtls_ssl_ciphersuite_t *ciphersuite_info =
- ssl->handshake->ciphersuite_info;
- int have_ca_chain = 0;
-
- int (*f_vrfy)(void *, mbedtls_x509_crt *, int, uint32_t *);
- void *p_vrfy;
-
- if (authmode == MBEDTLS_SSL_VERIFY_NONE) {
- return 0;
- }
-
- if (ssl->f_vrfy != NULL) {
- MBEDTLS_SSL_DEBUG_MSG(3, ("Use context-specific verification callback"));
- f_vrfy = ssl->f_vrfy;
- p_vrfy = ssl->p_vrfy;
- } else {
- MBEDTLS_SSL_DEBUG_MSG(3, ("Use configuration-specific verification callback"));
- f_vrfy = ssl->conf->f_vrfy;
- p_vrfy = ssl->conf->p_vrfy;
- }
-
- /*
- * Main check: verify certificate
- */
-#if defined(MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK)
- if (ssl->conf->f_ca_cb != NULL) {
- ((void) rs_ctx);
- have_ca_chain = 1;
-
- MBEDTLS_SSL_DEBUG_MSG(3, ("use CA callback for X.509 CRT verification"));
- ret = mbedtls_x509_crt_verify_with_ca_cb(
- chain,
- ssl->conf->f_ca_cb,
- ssl->conf->p_ca_cb,
- ssl->conf->cert_profile,
- ssl->hostname,
- &ssl->session_negotiate->verify_result,
- f_vrfy, p_vrfy);
- } else
-#endif /* MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK */
- {
- mbedtls_x509_crt *ca_chain;
- mbedtls_x509_crl *ca_crl;
-
-#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
- if (ssl->handshake->sni_ca_chain != NULL) {
- ca_chain = ssl->handshake->sni_ca_chain;
- ca_crl = ssl->handshake->sni_ca_crl;
- } else
-#endif
- {
- ca_chain = ssl->conf->ca_chain;
- ca_crl = ssl->conf->ca_crl;
- }
-
- if (ca_chain != NULL) {
- have_ca_chain = 1;
- }
-
- ret = mbedtls_x509_crt_verify_restartable(
- chain,
- ca_chain, ca_crl,
- ssl->conf->cert_profile,
- ssl->hostname,
- &ssl->session_negotiate->verify_result,
- f_vrfy, p_vrfy, rs_ctx);
- }
-
- if (ret != 0) {
- MBEDTLS_SSL_DEBUG_RET(1, "x509_verify_cert", ret);
- }
-
-#if defined(MBEDTLS_SSL_ECP_RESTARTABLE_ENABLED)
- if (ret == MBEDTLS_ERR_ECP_IN_PROGRESS) {
- return MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS;
- }
-#endif
-
- /*
- * Secondary checks: always done, but change 'ret' only if it was 0
- */
-
-#if defined(MBEDTLS_PK_HAVE_ECC_KEYS)
- {
- const mbedtls_pk_context *pk = &chain->pk;
-
- /* If certificate uses an EC key, make sure the curve is OK.
- * This is a public key, so it can't be opaque, so can_do() is a good
- * enough check to ensure pk_ec() is safe to use here. */
- if (mbedtls_pk_can_do(pk, MBEDTLS_PK_ECKEY)) {
- /* and in the unlikely case the above assumption no longer holds
- * we are making sure that pk_ec() here does not return a NULL
- */
- mbedtls_ecp_group_id grp_id = mbedtls_pk_get_ec_group_id(pk);
- if (grp_id == MBEDTLS_ECP_DP_NONE) {
- MBEDTLS_SSL_DEBUG_MSG(1, ("invalid group ID"));
- return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
- }
- if (mbedtls_ssl_check_curve(ssl, grp_id) != 0) {
- ssl->session_negotiate->verify_result |=
- MBEDTLS_X509_BADCERT_BAD_KEY;
-
- MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (EC key curve)"));
- if (ret == 0) {
- ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE;
- }
- }
- }
- }
-#endif /* MBEDTLS_PK_HAVE_ECC_KEYS */
-
- if (mbedtls_ssl_check_cert_usage(chain,
- ciphersuite_info,
- !ssl->conf->endpoint,
- &ssl->session_negotiate->verify_result) != 0) {
- MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (usage extensions)"));
- if (ret == 0) {
- ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE;
- }
- }
-
- /* mbedtls_x509_crt_verify_with_profile is supposed to report a
- * verification failure through MBEDTLS_ERR_X509_CERT_VERIFY_FAILED,
- * with details encoded in the verification flags. All other kinds
- * of error codes, including those from the user provided f_vrfy
- * functions, are treated as fatal and lead to a failure of
- * ssl_parse_certificate even if verification was optional. */
- if (authmode == MBEDTLS_SSL_VERIFY_OPTIONAL &&
- (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED ||
- ret == MBEDTLS_ERR_SSL_BAD_CERTIFICATE)) {
- ret = 0;
- }
-
- if (have_ca_chain == 0 && authmode == MBEDTLS_SSL_VERIFY_REQUIRED) {
- MBEDTLS_SSL_DEBUG_MSG(1, ("got no CA chain"));
- ret = MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED;
- }
-
- if (ret != 0) {
- uint8_t alert;
-
- /* The certificate may have been rejected for several reasons.
- Pick one and send the corresponding alert. Which alert to send
- may be a subject of debate in some cases. */
- if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_OTHER) {
- alert = MBEDTLS_SSL_ALERT_MSG_ACCESS_DENIED;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_CN_MISMATCH) {
- alert = MBEDTLS_SSL_ALERT_MSG_BAD_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_KEY_USAGE) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_EXT_KEY_USAGE) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_NS_CERT_TYPE) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_BAD_PK) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_BAD_KEY) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_EXPIRED) {
- alert = MBEDTLS_SSL_ALERT_MSG_CERT_EXPIRED;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_REVOKED) {
- alert = MBEDTLS_SSL_ALERT_MSG_CERT_REVOKED;
- } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_NOT_TRUSTED) {
- alert = MBEDTLS_SSL_ALERT_MSG_UNKNOWN_CA;
- } else {
- alert = MBEDTLS_SSL_ALERT_MSG_CERT_UNKNOWN;
- }
- mbedtls_ssl_send_alert_message(ssl, MBEDTLS_SSL_ALERT_LEVEL_FATAL,
- alert);
- }
-
-#if defined(MBEDTLS_DEBUG_C)
- if (ssl->session_negotiate->verify_result != 0) {
- MBEDTLS_SSL_DEBUG_MSG(3, ("! Certificate verification flags %08x",
- (unsigned int) ssl->session_negotiate->verify_result));
- } else {
- MBEDTLS_SSL_DEBUG_MSG(3, ("Certificate verification flags clear"));
- }
-#endif /* MBEDTLS_DEBUG_C */
-
- return ret;
-}
-
#if !defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE)
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_remember_peer_crt_digest(mbedtls_ssl_context *ssl,
@@ -8173,6 +7948,7 @@ int mbedtls_ssl_parse_certificate(mbedtls_ssl_context *ssl)
{
int ret = 0;
int crt_expected;
+ /* Authmode: precedence order is SNI if used else configuration */
#if defined(MBEDTLS_SSL_SRV_C) && defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
const int authmode = ssl->handshake->sni_authmode != MBEDTLS_SSL_VERIFY_UNSET
? ssl->handshake->sni_authmode
@@ -8252,8 +8028,9 @@ crt_verify:
}
#endif
- ret = ssl_parse_certificate_verify(ssl, authmode,
- chain, rs_ctx);
+ ret = mbedtls_ssl_verify_certificate(ssl, authmode, chain,
+ ssl->handshake->ciphersuite_info,
+ rs_ctx);
if (ret != 0) {
goto exit;
}
@@ -9919,4 +9696,274 @@ int mbedtls_ssl_session_set_ticket_alpn(mbedtls_ssl_session *session,
return 0;
}
#endif /* MBEDTLS_SSL_SRV_C && MBEDTLS_SSL_EARLY_DATA && MBEDTLS_SSL_ALPN */
+
+/*
+ * The following functions are used by 1.2 and 1.3, client and server.
+ */
+#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED)
+int mbedtls_ssl_check_cert_usage(const mbedtls_x509_crt *cert,
+ const mbedtls_ssl_ciphersuite_t *ciphersuite,
+ int recv_endpoint,
+ mbedtls_ssl_protocol_version tls_version,
+ uint32_t *flags)
+{
+ int ret = 0;
+ unsigned int usage = 0;
+ const char *ext_oid;
+ size_t ext_len;
+
+ /*
+ * keyUsage
+ */
+
+ /* Note: don't guard this with MBEDTLS_SSL_CLI_C because the server wants
+ * to check what a compliant client will think while choosing which cert
+ * to send to the client. */
+#if defined(MBEDTLS_SSL_PROTO_TLS1_2)
+ if (tls_version == MBEDTLS_SSL_VERSION_TLS1_2 &&
+ recv_endpoint == MBEDTLS_SSL_IS_CLIENT) {
+ /* TLS 1.2 server part of the key exchange */
+ switch (ciphersuite->key_exchange) {
+ case MBEDTLS_KEY_EXCHANGE_RSA:
+ case MBEDTLS_KEY_EXCHANGE_RSA_PSK:
+ usage = MBEDTLS_X509_KU_KEY_ENCIPHERMENT;
+ break;
+
+ case MBEDTLS_KEY_EXCHANGE_DHE_RSA:
+ case MBEDTLS_KEY_EXCHANGE_ECDHE_RSA:
+ case MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA:
+ usage = MBEDTLS_X509_KU_DIGITAL_SIGNATURE;
+ break;
+
+ case MBEDTLS_KEY_EXCHANGE_ECDH_RSA:
+ case MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA:
+ usage = MBEDTLS_X509_KU_KEY_AGREEMENT;
+ break;
+
+ /* Don't use default: we want warnings when adding new values */
+ case MBEDTLS_KEY_EXCHANGE_NONE:
+ case MBEDTLS_KEY_EXCHANGE_PSK:
+ case MBEDTLS_KEY_EXCHANGE_DHE_PSK:
+ case MBEDTLS_KEY_EXCHANGE_ECDHE_PSK:
+ case MBEDTLS_KEY_EXCHANGE_ECJPAKE:
+ usage = 0;
+ }
+ } else
+#endif
+ {
+ /* This is either TLS 1.3 authentication, which always uses signatures,
+ * or 1.2 client auth: rsa_sign and mbedtls_ecdsa_sign are the only
+ * options we implement, both using signatures. */
+ (void) tls_version;
+ (void) ciphersuite;
+ usage = MBEDTLS_X509_KU_DIGITAL_SIGNATURE;
+ }
+
+ if (mbedtls_x509_crt_check_key_usage(cert, usage) != 0) {
+ *flags |= MBEDTLS_X509_BADCERT_KEY_USAGE;
+ ret = -1;
+ }
+
+ /*
+ * extKeyUsage
+ */
+
+ if (recv_endpoint == MBEDTLS_SSL_IS_CLIENT) {
+ ext_oid = MBEDTLS_OID_SERVER_AUTH;
+ ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_SERVER_AUTH);
+ } else {
+ ext_oid = MBEDTLS_OID_CLIENT_AUTH;
+ ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_CLIENT_AUTH);
+ }
+
+ if (mbedtls_x509_crt_check_extended_key_usage(cert, ext_oid, ext_len) != 0) {
+ *flags |= MBEDTLS_X509_BADCERT_EXT_KEY_USAGE;
+ ret = -1;
+ }
+
+ return ret;
+}
+
+int mbedtls_ssl_verify_certificate(mbedtls_ssl_context *ssl,
+ int authmode,
+ mbedtls_x509_crt *chain,
+ const mbedtls_ssl_ciphersuite_t *ciphersuite_info,
+ void *rs_ctx)
+{
+ if (authmode == MBEDTLS_SSL_VERIFY_NONE) {
+ return 0;
+ }
+
+ /*
+ * Primary check: use the appropriate X.509 verification function
+ */
+ int (*f_vrfy)(void *, mbedtls_x509_crt *, int, uint32_t *);
+ void *p_vrfy;
+ if (ssl->f_vrfy != NULL) {
+ MBEDTLS_SSL_DEBUG_MSG(3, ("Use context-specific verification callback"));
+ f_vrfy = ssl->f_vrfy;
+ p_vrfy = ssl->p_vrfy;
+ } else {
+ MBEDTLS_SSL_DEBUG_MSG(3, ("Use configuration-specific verification callback"));
+ f_vrfy = ssl->conf->f_vrfy;
+ p_vrfy = ssl->conf->p_vrfy;
+ }
+
+ int ret = 0;
+ int have_ca_chain_or_callback = 0;
+#if defined(MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK)
+ if (ssl->conf->f_ca_cb != NULL) {
+ ((void) rs_ctx);
+ have_ca_chain_or_callback = 1;
+
+ MBEDTLS_SSL_DEBUG_MSG(3, ("use CA callback for X.509 CRT verification"));
+ ret = mbedtls_x509_crt_verify_with_ca_cb(
+ chain,
+ ssl->conf->f_ca_cb,
+ ssl->conf->p_ca_cb,
+ ssl->conf->cert_profile,
+ ssl->hostname,
+ &ssl->session_negotiate->verify_result,
+ f_vrfy, p_vrfy);
+ } else
+#endif /* MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK */
+ {
+ mbedtls_x509_crt *ca_chain;
+ mbedtls_x509_crl *ca_crl;
+#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
+ if (ssl->handshake->sni_ca_chain != NULL) {
+ ca_chain = ssl->handshake->sni_ca_chain;
+ ca_crl = ssl->handshake->sni_ca_crl;
+ } else
+#endif
+ {
+ ca_chain = ssl->conf->ca_chain;
+ ca_crl = ssl->conf->ca_crl;
+ }
+
+ if (ca_chain != NULL) {
+ have_ca_chain_or_callback = 1;
+ }
+
+ ret = mbedtls_x509_crt_verify_restartable(
+ chain,
+ ca_chain, ca_crl,
+ ssl->conf->cert_profile,
+ ssl->hostname,
+ &ssl->session_negotiate->verify_result,
+ f_vrfy, p_vrfy, rs_ctx);
+ }
+
+ if (ret != 0) {
+ MBEDTLS_SSL_DEBUG_RET(1, "x509_verify_cert", ret);
+ }
+
+#if defined(MBEDTLS_SSL_ECP_RESTARTABLE_ENABLED)
+ if (ret == MBEDTLS_ERR_ECP_IN_PROGRESS) {
+ return MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS;
+ }
+#endif
+
+ /*
+ * Secondary checks: always done, but change 'ret' only if it was 0
+ */
+
+ /* With TLS 1.2 and ECC certs, check that the curve used by the
+ * certificate is on our list of acceptable curves.
+ *
+ * With TLS 1.3 this is not needed because the curve is part of the
+ * signature algorithm (eg ecdsa_secp256r1_sha256) which is checked when
+ * we validate the signature made with the key associated to this cert.
+ */
+#if defined(MBEDTLS_SSL_PROTO_TLS1_2) && \
+ defined(MBEDTLS_PK_HAVE_ECC_KEYS)
+ if (ssl->tls_version == MBEDTLS_SSL_VERSION_TLS1_2 &&
+ mbedtls_pk_can_do(&chain->pk, MBEDTLS_PK_ECKEY)) {
+ if (mbedtls_ssl_check_curve(ssl, mbedtls_pk_get_ec_group_id(&chain->pk)) != 0) {
+ MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (EC key curve)"));
+ ssl->session_negotiate->verify_result |= MBEDTLS_X509_BADCERT_BAD_KEY;
+ if (ret == 0) {
+ ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE;
+ }
+ }
+ }
+#endif /* MBEDTLS_SSL_PROTO_TLS1_2 && MBEDTLS_PK_HAVE_ECC_KEYS */
+
+ /* Check X.509 usage extensions (keyUsage, extKeyUsage) */
+ if (mbedtls_ssl_check_cert_usage(chain,
+ ciphersuite_info,
+ ssl->conf->endpoint,
+ ssl->tls_version,
+ &ssl->session_negotiate->verify_result) != 0) {
+ MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (usage extensions)"));
+ if (ret == 0) {
+ ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE;
+ }
+ }
+
+ /* With authmode optional, we want to keep going if the certificate was
+ * unacceptable, but still fail on other errors (out of memory etc),
+ * including fatal errors from the f_vrfy callback.
+ *
+ * The only acceptable errors are:
+ * - MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: cert rejected by primary check;
+ * - MBEDTLS_ERR_SSL_BAD_CERTIFICATE: cert rejected by secondary checks.
+ * Anything else is a fatal error. */
+ if (authmode == MBEDTLS_SSL_VERIFY_OPTIONAL &&
+ (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED ||
+ ret == MBEDTLS_ERR_SSL_BAD_CERTIFICATE)) {
+ ret = 0;
+ }
+
+ /* Return a specific error as this is a user error: inconsistent
+ * configuration - can't verify without trust anchors. */
+ if (have_ca_chain_or_callback == 0 && authmode == MBEDTLS_SSL_VERIFY_REQUIRED) {
+ MBEDTLS_SSL_DEBUG_MSG(1, ("got no CA chain"));
+ ret = MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED;
+ }
+
+ if (ret != 0) {
+ uint8_t alert;
+
+ /* The certificate may have been rejected for several reasons.
+ Pick one and send the corresponding alert. Which alert to send
+ may be a subject of debate in some cases. */
+ if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_OTHER) {
+ alert = MBEDTLS_SSL_ALERT_MSG_ACCESS_DENIED;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_CN_MISMATCH) {
+ alert = MBEDTLS_SSL_ALERT_MSG_BAD_CERT;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_KEY_USAGE) {
+ alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_EXT_KEY_USAGE) {
+ alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_BAD_PK) {
+ alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_BAD_KEY) {
+ alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_EXPIRED) {
+ alert = MBEDTLS_SSL_ALERT_MSG_CERT_EXPIRED;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_REVOKED) {
+ alert = MBEDTLS_SSL_ALERT_MSG_CERT_REVOKED;
+ } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_NOT_TRUSTED) {
+ alert = MBEDTLS_SSL_ALERT_MSG_UNKNOWN_CA;
+ } else {
+ alert = MBEDTLS_SSL_ALERT_MSG_CERT_UNKNOWN;
+ }
+ mbedtls_ssl_send_alert_message(ssl, MBEDTLS_SSL_ALERT_LEVEL_FATAL,
+ alert);
+ }
+
+#if defined(MBEDTLS_DEBUG_C)
+ if (ssl->session_negotiate->verify_result != 0) {
+ MBEDTLS_SSL_DEBUG_MSG(3, ("! Certificate verification flags %08x",
+ (unsigned int) ssl->session_negotiate->verify_result));
+ } else {
+ MBEDTLS_SSL_DEBUG_MSG(3, ("Certificate verification flags clear"));
+ }
+#endif /* MBEDTLS_DEBUG_C */
+
+ return ret;
+}
+#endif /* MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED */
+
#endif /* MBEDTLS_SSL_TLS_C */
diff --git a/thirdparty/mbedtls/library/ssl_tls12_client.c b/thirdparty/mbedtls/library/ssl_tls12_client.c
index eac6a3aadd..9b2da5a39d 100644
--- a/thirdparty/mbedtls/library/ssl_tls12_client.c
+++ b/thirdparty/mbedtls/library/ssl_tls12_client.c
@@ -364,7 +364,8 @@ static int ssl_write_session_ticket_ext(mbedtls_ssl_context *ssl,
*olen = 0;
- if (ssl->conf->session_tickets == MBEDTLS_SSL_SESSION_TICKETS_DISABLED) {
+ if (mbedtls_ssl_conf_get_session_tickets(ssl->conf) ==
+ MBEDTLS_SSL_SESSION_TICKETS_DISABLED) {
return 0;
}
@@ -787,7 +788,8 @@ static int ssl_parse_session_ticket_ext(mbedtls_ssl_context *ssl,
const unsigned char *buf,
size_t len)
{
- if (ssl->conf->session_tickets == MBEDTLS_SSL_SESSION_TICKETS_DISABLED ||
+ if ((mbedtls_ssl_conf_get_session_tickets(ssl->conf) ==
+ MBEDTLS_SSL_SESSION_TICKETS_DISABLED) ||
len != 0) {
MBEDTLS_SSL_DEBUG_MSG(1,
("non-matching session ticket extension"));
diff --git a/thirdparty/mbedtls/library/ssl_tls12_server.c b/thirdparty/mbedtls/library/ssl_tls12_server.c
index b49a8ae6a6..03722ac33c 100644
--- a/thirdparty/mbedtls/library/ssl_tls12_server.c
+++ b/thirdparty/mbedtls/library/ssl_tls12_server.c
@@ -756,7 +756,9 @@ static int ssl_pick_cert(mbedtls_ssl_context *ssl,
* and decrypting with the same RSA key.
*/
if (mbedtls_ssl_check_cert_usage(cur->cert, ciphersuite_info,
- MBEDTLS_SSL_IS_SERVER, &flags) != 0) {
+ MBEDTLS_SSL_IS_CLIENT,
+ MBEDTLS_SSL_VERSION_TLS1_2,
+ &flags) != 0) {
MBEDTLS_SSL_DEBUG_MSG(3, ("certificate mismatch: "
"(extended) key usage extension"));
continue;
@@ -2631,13 +2633,8 @@ static int ssl_get_ecdh_params_from_cert(mbedtls_ssl_context *ssl)
ssl->handshake->xxdh_psa_type = psa_get_key_type(&key_attributes);
ssl->handshake->xxdh_psa_bits = psa_get_key_bits(&key_attributes);
- if (pk_type == MBEDTLS_PK_OPAQUE) {
- /* Opaque key is created by the user (externally from Mbed TLS)
- * so we assume it already has the right algorithm and flags
- * set. Just copy its ID as reference. */
- ssl->handshake->xxdh_psa_privkey = pk->priv_id;
- ssl->handshake->xxdh_psa_privkey_is_external = 1;
- } else {
+#if defined(MBEDTLS_PK_USE_PSA_EC_DATA)
+ if (pk_type != MBEDTLS_PK_OPAQUE) {
/* PK_ECKEY[_DH] and PK_ECDSA instead as parsed from the PK
* module and only have ECDSA capabilities. Since we need
* them for ECDH later, we export and then re-import them with
@@ -2665,10 +2662,20 @@ static int ssl_get_ecdh_params_from_cert(mbedtls_ssl_context *ssl)
/* Set this key as owned by the TLS library: it will be its duty
* to clear it exit. */
ssl->handshake->xxdh_psa_privkey_is_external = 0;
+
+ ret = 0;
+ break;
}
+#endif /* MBEDTLS_PK_USE_PSA_EC_DATA */
+ /* Opaque key is created by the user (externally from Mbed TLS)
+ * so we assume it already has the right algorithm and flags
+ * set. Just copy its ID as reference. */
+ ssl->handshake->xxdh_psa_privkey = pk->priv_id;
+ ssl->handshake->xxdh_psa_privkey_is_external = 1;
ret = 0;
break;
+
#if !defined(MBEDTLS_PK_USE_PSA_EC_DATA)
case MBEDTLS_PK_ECKEY:
case MBEDTLS_PK_ECKEY_DH:
@@ -3916,7 +3923,7 @@ static int ssl_parse_client_key_exchange(mbedtls_ssl_context *ssl)
#if defined(MBEDTLS_USE_PSA_CRYPTO)
psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
psa_status_t destruction_status = PSA_ERROR_CORRUPTION_DETECTED;
- uint8_t ecpoint_len;
+ size_t ecpoint_len;
mbedtls_ssl_handshake_params *handshake = ssl->handshake;
diff --git a/thirdparty/mbedtls/library/ssl_tls13_client.c b/thirdparty/mbedtls/library/ssl_tls13_client.c
index 7fcc394319..b63b5e63c5 100644
--- a/thirdparty/mbedtls/library/ssl_tls13_client.c
+++ b/thirdparty/mbedtls/library/ssl_tls13_client.c
@@ -666,6 +666,7 @@ static int ssl_tls13_write_psk_key_exchange_modes_ext(mbedtls_ssl_context *ssl,
return 0;
}
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
static psa_algorithm_t ssl_tls13_get_ciphersuite_hash_alg(int ciphersuite)
{
const mbedtls_ssl_ciphersuite_t *ciphersuite_info = NULL;
@@ -678,7 +679,6 @@ static psa_algorithm_t ssl_tls13_get_ciphersuite_hash_alg(int ciphersuite)
return PSA_ALG_NONE;
}
-#if defined(MBEDTLS_SSL_SESSION_TICKETS)
static int ssl_tls13_has_configured_ticket(mbedtls_ssl_context *ssl)
{
mbedtls_ssl_session *session = ssl->session_negotiate;
@@ -1141,6 +1141,11 @@ int mbedtls_ssl_tls13_write_client_hello_exts(mbedtls_ssl_context *ssl,
*out_len = 0;
+ ret = mbedtls_ssl_tls13_crypto_init(ssl);
+ if (ret != 0) {
+ return ret;
+ }
+
/* Write supported_versions extension
*
* Supported Versions Extension is mandatory with TLS 1.3.
diff --git a/thirdparty/mbedtls/library/ssl_tls13_generic.c b/thirdparty/mbedtls/library/ssl_tls13_generic.c
index d448a054a9..b6d09788ba 100644
--- a/thirdparty/mbedtls/library/ssl_tls13_generic.c
+++ b/thirdparty/mbedtls/library/ssl_tls13_generic.c
@@ -27,7 +27,6 @@
#include "psa/crypto.h"
#include "psa_util_internal.h"
-#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_SOME_EPHEMERAL_ENABLED)
/* Define a local translating function to save code size by not using too many
* arguments in each translating place. */
static int local_err_translation(psa_status_t status)
@@ -37,7 +36,16 @@ static int local_err_translation(psa_status_t status)
psa_generic_status_to_mbedtls);
}
#define PSA_TO_MBEDTLS_ERR(status) local_err_translation(status)
-#endif
+
+int mbedtls_ssl_tls13_crypto_init(mbedtls_ssl_context *ssl)
+{
+ psa_status_t status = psa_crypto_init();
+ if (status != PSA_SUCCESS) {
+ (void) ssl; // unused when debugging is disabled
+ MBEDTLS_SSL_DEBUG_RET(1, "psa_crypto_init", status);
+ }
+ return PSA_TO_MBEDTLS_ERR(status);
+}
const uint8_t mbedtls_ssl_tls13_hello_retry_request_magic[
MBEDTLS_SERVER_HELLO_RANDOM_LEN] =
@@ -193,10 +201,12 @@ static void ssl_tls13_create_verify_structure(const unsigned char *transcript_ha
idx = 64;
if (from == MBEDTLS_SSL_IS_CLIENT) {
- memcpy(verify_buffer + idx, MBEDTLS_SSL_TLS1_3_LBL_WITH_LEN(client_cv));
+ memcpy(verify_buffer + idx, mbedtls_ssl_tls13_labels.client_cv,
+ MBEDTLS_SSL_TLS1_3_LBL_LEN(client_cv));
idx += MBEDTLS_SSL_TLS1_3_LBL_LEN(client_cv);
} else { /* from == MBEDTLS_SSL_IS_SERVER */
- memcpy(verify_buffer + idx, MBEDTLS_SSL_TLS1_3_LBL_WITH_LEN(server_cv));
+ memcpy(verify_buffer + idx, mbedtls_ssl_tls13_labels.server_cv,
+ MBEDTLS_SSL_TLS1_3_LBL_LEN(server_cv));
idx += MBEDTLS_SSL_TLS1_3_LBL_LEN(server_cv);
}
@@ -470,6 +480,7 @@ int mbedtls_ssl_tls13_parse_certificate(mbedtls_ssl_context *ssl,
mbedtls_free(ssl->session_negotiate->peer_cert);
}
+ /* This is used by ssl_tls13_validate_certificate() */
if (certificate_list_len == 0) {
ssl->session_negotiate->peer_cert = NULL;
ret = 0;
@@ -625,25 +636,13 @@ int mbedtls_ssl_tls13_parse_certificate(mbedtls_ssl_context *ssl,
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_validate_certificate(mbedtls_ssl_context *ssl)
{
- int ret = 0;
- int authmode = MBEDTLS_SSL_VERIFY_REQUIRED;
- mbedtls_x509_crt *ca_chain;
- mbedtls_x509_crl *ca_crl;
- const char *ext_oid;
- size_t ext_len;
- uint32_t verify_result = 0;
-
- /* If SNI was used, overwrite authentication mode
- * from the configuration. */
-#if defined(MBEDTLS_SSL_SRV_C)
- if (ssl->conf->endpoint == MBEDTLS_SSL_IS_SERVER) {
-#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
- if (ssl->handshake->sni_authmode != MBEDTLS_SSL_VERIFY_UNSET) {
- authmode = ssl->handshake->sni_authmode;
- } else
-#endif
- authmode = ssl->conf->authmode;
- }
+ /* Authmode: precedence order is SNI if used else configuration */
+#if defined(MBEDTLS_SSL_SRV_C) && defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
+ const int authmode = ssl->handshake->sni_authmode != MBEDTLS_SSL_VERIFY_UNSET
+ ? ssl->handshake->sni_authmode
+ : ssl->conf->authmode;
+#else
+ const int authmode = ssl->conf->authmode;
#endif
/*
@@ -675,6 +674,11 @@ static int ssl_tls13_validate_certificate(mbedtls_ssl_context *ssl)
#endif /* MBEDTLS_SSL_SRV_C */
#if defined(MBEDTLS_SSL_CLI_C)
+ /* Regardless of authmode, the server is not allowed to send an empty
+ * certificate chain. (Last paragraph before 4.4.2.1 in RFC 8446: "The
+ * server's certificate_list MUST always be non-empty.") With authmode
+ * optional/none, we continue the handshake if we can't validate the
+ * server's cert, but we still break it if no certificate was sent. */
if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT) {
MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_NO_CERT,
MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE);
@@ -683,114 +687,9 @@ static int ssl_tls13_validate_certificate(mbedtls_ssl_context *ssl)
#endif /* MBEDTLS_SSL_CLI_C */
}
-#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION)
- if (ssl->handshake->sni_ca_chain != NULL) {
- ca_chain = ssl->handshake->sni_ca_chain;
- ca_crl = ssl->handshake->sni_ca_crl;
- } else
-#endif /* MBEDTLS_SSL_SERVER_NAME_INDICATION */
- {
- ca_chain = ssl->conf->ca_chain;
- ca_crl = ssl->conf->ca_crl;
- }
-
- /*
- * Main check: verify certificate
- */
- ret = mbedtls_x509_crt_verify_with_profile(
- ssl->session_negotiate->peer_cert,
- ca_chain, ca_crl,
- ssl->conf->cert_profile,
- ssl->hostname,
- &verify_result,
- ssl->conf->f_vrfy, ssl->conf->p_vrfy);
-
- if (ret != 0) {
- MBEDTLS_SSL_DEBUG_RET(1, "x509_verify_cert", ret);
- }
-
- /*
- * Secondary checks: always done, but change 'ret' only if it was 0
- */
- if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT) {
- ext_oid = MBEDTLS_OID_SERVER_AUTH;
- ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_SERVER_AUTH);
- } else {
- ext_oid = MBEDTLS_OID_CLIENT_AUTH;
- ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_CLIENT_AUTH);
- }
-
- if ((mbedtls_x509_crt_check_key_usage(
- ssl->session_negotiate->peer_cert,
- MBEDTLS_X509_KU_DIGITAL_SIGNATURE) != 0) ||
- (mbedtls_x509_crt_check_extended_key_usage(
- ssl->session_negotiate->peer_cert,
- ext_oid, ext_len) != 0)) {
- MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (usage extensions)"));
- if (ret == 0) {
- ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE;
- }
- }
-
- /* mbedtls_x509_crt_verify_with_profile is supposed to report a
- * verification failure through MBEDTLS_ERR_X509_CERT_VERIFY_FAILED,
- * with details encoded in the verification flags. All other kinds
- * of error codes, including those from the user provided f_vrfy
- * functions, are treated as fatal and lead to a failure of
- * mbedtls_ssl_tls13_parse_certificate even if verification was optional.
- */
- if (authmode == MBEDTLS_SSL_VERIFY_OPTIONAL &&
- (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED ||
- ret == MBEDTLS_ERR_SSL_BAD_CERTIFICATE)) {
- ret = 0;
- }
-
- if (ca_chain == NULL && authmode == MBEDTLS_SSL_VERIFY_REQUIRED) {
- MBEDTLS_SSL_DEBUG_MSG(1, ("got no CA chain"));
- ret = MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED;
- }
-
- if (ret != 0) {
- /* The certificate may have been rejected for several reasons.
- Pick one and send the corresponding alert. Which alert to send
- may be a subject of debate in some cases. */
- if (verify_result & MBEDTLS_X509_BADCERT_OTHER) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(
- MBEDTLS_SSL_ALERT_MSG_ACCESS_DENIED, ret);
- } else if (verify_result & MBEDTLS_X509_BADCERT_CN_MISMATCH) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_BAD_CERT, ret);
- } else if (verify_result & (MBEDTLS_X509_BADCERT_KEY_USAGE |
- MBEDTLS_X509_BADCERT_EXT_KEY_USAGE |
- MBEDTLS_X509_BADCERT_NS_CERT_TYPE |
- MBEDTLS_X509_BADCERT_BAD_PK |
- MBEDTLS_X509_BADCERT_BAD_KEY)) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(
- MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT, ret);
- } else if (verify_result & MBEDTLS_X509_BADCERT_EXPIRED) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(
- MBEDTLS_SSL_ALERT_MSG_CERT_EXPIRED, ret);
- } else if (verify_result & MBEDTLS_X509_BADCERT_REVOKED) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(
- MBEDTLS_SSL_ALERT_MSG_CERT_REVOKED, ret);
- } else if (verify_result & MBEDTLS_X509_BADCERT_NOT_TRUSTED) {
- MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_UNKNOWN_CA, ret);
- } else {
- MBEDTLS_SSL_PEND_FATAL_ALERT(
- MBEDTLS_SSL_ALERT_MSG_CERT_UNKNOWN, ret);
- }
- }
-
-#if defined(MBEDTLS_DEBUG_C)
- if (verify_result != 0) {
- MBEDTLS_SSL_DEBUG_MSG(3, ("! Certificate verification flags %08x",
- (unsigned int) verify_result));
- } else {
- MBEDTLS_SSL_DEBUG_MSG(3, ("Certificate verification flags clear"));
- }
-#endif /* MBEDTLS_DEBUG_C */
-
- ssl->session_negotiate->verify_result = verify_result;
- return ret;
+ return mbedtls_ssl_verify_certificate(ssl, authmode,
+ ssl->session_negotiate->peer_cert,
+ NULL, NULL);
}
#else /* MBEDTLS_SSL_KEEP_PEER_CERTIFICATE */
MBEDTLS_CHECK_RETURN_CRITICAL
@@ -1482,9 +1381,11 @@ int mbedtls_ssl_tls13_check_early_data_len(mbedtls_ssl_context *ssl,
ssl->total_early_data_size)) {
MBEDTLS_SSL_DEBUG_MSG(
- 2, ("EarlyData: Too much early data received, %u + %" MBEDTLS_PRINTF_SIZET " > %u",
- ssl->total_early_data_size, early_data_len,
- ssl->session_negotiate->max_early_data_size));
+ 2, ("EarlyData: Too much early data received, "
+ "%lu + %" MBEDTLS_PRINTF_SIZET " > %lu",
+ (unsigned long) ssl->total_early_data_size,
+ early_data_len,
+ (unsigned long) ssl->session_negotiate->max_early_data_size));
MBEDTLS_SSL_PEND_FATAL_ALERT(
MBEDTLS_SSL_ALERT_MSG_UNEXPECTED_MESSAGE,
diff --git a/thirdparty/mbedtls/library/ssl_tls13_server.c b/thirdparty/mbedtls/library/ssl_tls13_server.c
index 2760d76a5d..693edc7b0b 100644
--- a/thirdparty/mbedtls/library/ssl_tls13_server.c
+++ b/thirdparty/mbedtls/library/ssl_tls13_server.c
@@ -92,8 +92,9 @@ static void ssl_tls13_select_ciphersuite(
return;
}
- MBEDTLS_SSL_DEBUG_MSG(2, ("No matched ciphersuite, psk_ciphersuite_id=%x, psk_hash_alg=%x",
- (unsigned) psk_ciphersuite_id, psk_hash_alg));
+ MBEDTLS_SSL_DEBUG_MSG(2, ("No matched ciphersuite, psk_ciphersuite_id=%x, psk_hash_alg=%lx",
+ (unsigned) psk_ciphersuite_id,
+ (unsigned long) psk_hash_alg));
}
#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_SOME_PSK_ENABLED)
@@ -172,12 +173,12 @@ static int ssl_tls13_parse_key_exchange_modes_ext(mbedtls_ssl_context *ssl,
#define SSL_TLS1_3_PSK_IDENTITY_MATCH_BUT_PSK_NOT_USABLE 1
#define SSL_TLS1_3_PSK_IDENTITY_MATCH 0
-#if defined(MBEDTLS_SSL_SESSION_TICKETS)
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_key_exchange_is_psk_available(mbedtls_ssl_context *ssl);
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_key_exchange_is_psk_ephemeral_available(mbedtls_ssl_context *ssl);
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_offered_psks_check_identity_match_ticket(
mbedtls_ssl_context *ssl,
@@ -575,10 +576,8 @@ static int ssl_tls13_parse_pre_shared_key_ext(
psa_algorithm_t psk_hash_alg;
int allowed_key_exchange_modes;
-#if defined(MBEDTLS_SSL_SESSION_TICKETS)
mbedtls_ssl_session session;
mbedtls_ssl_session_init(&session);
-#endif
MBEDTLS_SSL_CHK_BUF_READ_PTR(p_identity_len, identities_end, 2 + 1 + 4);
identity_len = MBEDTLS_GET_UINT16_BE(p_identity_len, 0);
@@ -1356,19 +1355,23 @@ static int ssl_tls13_parse_client_hello(mbedtls_ssl_context *ssl,
* compression methods and the length of the extensions.
*
* cipher_suites cipher_suites_len bytes
- * legacy_compression_methods 2 bytes
- * extensions_len 2 bytes
+ * legacy_compression_methods length 1 byte
*/
- MBEDTLS_SSL_CHK_BUF_READ_PTR(p, end, cipher_suites_len + 2 + 2);
+ MBEDTLS_SSL_CHK_BUF_READ_PTR(p, end, cipher_suites_len + 1);
p += cipher_suites_len;
cipher_suites_end = p;
+ /* Check if we have enough data for legacy_compression_methods
+ * and the length of the extensions (2 bytes).
+ */
+ MBEDTLS_SSL_CHK_BUF_READ_PTR(p + 1, end, p[0] + 2);
+
/*
* Search for the supported versions extension and parse it to determine
* if the client supports TLS 1.3.
*/
ret = mbedtls_ssl_tls13_is_supported_versions_ext_present_in_exts(
- ssl, p + 2, end,
+ ssl, p + 1 + p[0], end,
&supported_versions_data, &supported_versions_data_end);
if (ret < 0) {
MBEDTLS_SSL_DEBUG_RET(1,
@@ -1409,6 +1412,12 @@ static int ssl_tls13_parse_client_hello(mbedtls_ssl_context *ssl,
ssl->session_negotiate->tls_version = MBEDTLS_SSL_VERSION_TLS1_3;
ssl->session_negotiate->endpoint = ssl->conf->endpoint;
+ /* Before doing any crypto, make sure we can. */
+ ret = mbedtls_ssl_tls13_crypto_init(ssl);
+ if (ret != 0) {
+ return ret;
+ }
+
/*
* We are negotiating the version 1.3 of the protocol. Do what we have
* postponed: copy of the client random bytes, copy of the legacy session
@@ -3109,6 +3118,7 @@ static int ssl_tls13_handshake_wrapup(mbedtls_ssl_context *ssl)
return 0;
}
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
/*
* Handler for MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET
*/
@@ -3138,7 +3148,6 @@ static int ssl_tls13_write_new_session_ticket_coordinate(mbedtls_ssl_context *ss
return SSL_NEW_SESSION_TICKET_WRITE;
}
-#if defined(MBEDTLS_SSL_SESSION_TICKETS)
MBEDTLS_CHECK_RETURN_CRITICAL
static int ssl_tls13_prepare_new_session_ticket(mbedtls_ssl_context *ssl,
unsigned char *ticket_nonce,
diff --git a/thirdparty/mbedtls/library/version_features.c b/thirdparty/mbedtls/library/version_features.c
index 406161d4c7..f542d9808f 100644
--- a/thirdparty/mbedtls/library/version_features.c
+++ b/thirdparty/mbedtls/library/version_features.c
@@ -423,6 +423,9 @@ static const char * const features[] = {
#if defined(MBEDTLS_PSA_CRYPTO_SPM)
"PSA_CRYPTO_SPM", //no-check-names
#endif /* MBEDTLS_PSA_CRYPTO_SPM */
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+ "PSA_KEY_STORE_DYNAMIC", //no-check-names
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
#if defined(MBEDTLS_PSA_P256M_DRIVER_ENABLED)
"PSA_P256M_DRIVER_ENABLED", //no-check-names
#endif /* MBEDTLS_PSA_P256M_DRIVER_ENABLED */
diff --git a/thirdparty/mbedtls/library/x509_crt.c b/thirdparty/mbedtls/library/x509_crt.c
index 2fd56fbd79..53cdcf0266 100644
--- a/thirdparty/mbedtls/library/x509_crt.c
+++ b/thirdparty/mbedtls/library/x509_crt.c
@@ -48,7 +48,9 @@
#if defined(MBEDTLS_HAVE_TIME)
#if defined(_WIN32) && !defined(EFIX64) && !defined(EFI32)
+#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
+#endif
#include <windows.h>
#else
#include <time.h>
diff --git a/thirdparty/mbedtls/library/x509write_crt.c b/thirdparty/mbedtls/library/x509write_crt.c
index 72f5a10a17..56f23c9fab 100644
--- a/thirdparty/mbedtls/library/x509write_crt.c
+++ b/thirdparty/mbedtls/library/x509write_crt.c
@@ -46,6 +46,10 @@ void mbedtls_x509write_crt_init(mbedtls_x509write_cert *ctx)
void mbedtls_x509write_crt_free(mbedtls_x509write_cert *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_asn1_free_named_data_list(&ctx->subject);
mbedtls_asn1_free_named_data_list(&ctx->issuer);
mbedtls_asn1_free_named_data_list(&ctx->extensions);
diff --git a/thirdparty/mbedtls/library/x509write_csr.c b/thirdparty/mbedtls/library/x509write_csr.c
index d3ddbcc03d..0d6f6bb1d3 100644
--- a/thirdparty/mbedtls/library/x509write_csr.c
+++ b/thirdparty/mbedtls/library/x509write_csr.c
@@ -43,6 +43,10 @@ void mbedtls_x509write_csr_init(mbedtls_x509write_csr *ctx)
void mbedtls_x509write_csr_free(mbedtls_x509write_csr *ctx)
{
+ if (ctx == NULL) {
+ return;
+ }
+
mbedtls_asn1_free_named_data_list(&ctx->subject);
mbedtls_asn1_free_named_data_list(&ctx->extensions);
diff --git a/thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff b/thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff
index c5f1970223..3a15928fe1 100644
--- a/thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff
+++ b/thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff
@@ -1,5 +1,5 @@
diff --git a/thirdparty/mbedtls/include/psa/crypto.h b/thirdparty/mbedtls/include/psa/crypto.h
-index 92f9c824e9..1cc2e7e729 100644
+index 2bbcea3ee0..96baf8f3ed 100644
--- a/thirdparty/mbedtls/include/psa/crypto.h
+++ b/thirdparty/mbedtls/include/psa/crypto.h
@@ -107,7 +107,9 @@ psa_status_t psa_crypto_init(void);
@@ -12,7 +12,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Declare a key as persistent and set its key identifier.
*
-@@ -333,7 +335,9 @@ static void psa_set_key_bits(psa_key_attributes_t *attributes,
+@@ -336,7 +338,9 @@ static void psa_set_key_bits(psa_key_attributes_t *attributes,
*
* \return The key type stored in the attribute structure.
*/
@@ -22,7 +22,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Retrieve the key size from key attributes.
*
-@@ -936,7 +940,9 @@ typedef struct psa_hash_operation_s psa_hash_operation_t;
+@@ -939,7 +943,9 @@ typedef struct psa_hash_operation_s psa_hash_operation_t;
/** Return an initial value for a hash operation object.
*/
@@ -32,7 +32,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Set up a multipart hash operation.
*
-@@ -1295,7 +1301,9 @@ typedef struct psa_mac_operation_s psa_mac_operation_t;
+@@ -1298,7 +1304,9 @@ typedef struct psa_mac_operation_s psa_mac_operation_t;
/** Return an initial value for a MAC operation object.
*/
@@ -42,7 +42,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Set up a multipart MAC calculation operation.
*
-@@ -1708,7 +1716,9 @@ typedef struct psa_cipher_operation_s psa_cipher_operation_t;
+@@ -1711,7 +1719,9 @@ typedef struct psa_cipher_operation_s psa_cipher_operation_t;
/** Return an initial value for a cipher operation object.
*/
@@ -52,7 +52,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Set the key for a multipart symmetric encryption operation.
*
-@@ -2226,7 +2236,9 @@ typedef struct psa_aead_operation_s psa_aead_operation_t;
+@@ -2229,7 +2239,9 @@ typedef struct psa_aead_operation_s psa_aead_operation_t;
/** Return an initial value for an AEAD operation object.
*/
@@ -62,7 +62,7 @@ index 92f9c824e9..1cc2e7e729 100644
/** Set the key for a multipart authenticated encryption operation.
*
-@@ -3213,7 +3225,9 @@ typedef struct psa_key_derivation_s psa_key_derivation_operation_t;
+@@ -3216,7 +3228,9 @@ typedef struct psa_key_derivation_s psa_key_derivation_operation_t;
/** Return an initial value for a key derivation operation object.
*/
@@ -73,10 +73,10 @@ index 92f9c824e9..1cc2e7e729 100644
/** Set up a key derivation operation.
*
diff --git a/thirdparty/mbedtls/include/psa/crypto_extra.h b/thirdparty/mbedtls/include/psa/crypto_extra.h
-index 6ed1f6c43a..2686b9d74d 100644
+index 0cf42c6055..d276cd4c7f 100644
--- a/thirdparty/mbedtls/include/psa/crypto_extra.h
+++ b/thirdparty/mbedtls/include/psa/crypto_extra.h
-@@ -915,7 +915,9 @@ typedef struct psa_pake_cipher_suite_s psa_pake_cipher_suite_t;
+@@ -923,7 +923,9 @@ typedef struct psa_pake_cipher_suite_s psa_pake_cipher_suite_t;
/** Return an initial value for a PAKE cipher suite object.
*/
@@ -86,7 +86,7 @@ index 6ed1f6c43a..2686b9d74d 100644
/** Retrieve the PAKE algorithm from a PAKE cipher suite.
*
-@@ -1048,7 +1050,9 @@ typedef struct psa_jpake_computation_stage_s psa_jpake_computation_stage_t;
+@@ -1056,7 +1058,9 @@ typedef struct psa_jpake_computation_stage_s psa_jpake_computation_stage_t;
/** Return an initial value for a PAKE operation object.
*/
diff --git a/thirdparty/mbedtls/patches/no-flexible-arrays.diff b/thirdparty/mbedtls/patches/no-flexible-arrays.diff
deleted file mode 100644
index 87fd06f1e3..0000000000
--- a/thirdparty/mbedtls/patches/no-flexible-arrays.diff
+++ /dev/null
@@ -1,132 +0,0 @@
-diff --git a/thirdparty/mbedtls/include/psa/crypto.h b/thirdparty/mbedtls/include/psa/crypto.h
-index 7083bd911b..92f9c824e9 100644
---- a/thirdparty/mbedtls/include/psa/crypto.h
-+++ b/thirdparty/mbedtls/include/psa/crypto.h
-@@ -3834,12 +3834,14 @@ psa_status_t psa_key_derivation_output_key(
- * It is implementation-dependent whether a failure to initialize
- * results in this error code.
- */
-+#ifndef __cplusplus
- psa_status_t psa_key_derivation_output_key_ext(
- const psa_key_attributes_t *attributes,
- psa_key_derivation_operation_t *operation,
- const psa_key_production_parameters_t *params,
- size_t params_data_length,
- mbedtls_svc_key_id_t *key);
-+#endif
-
- /** Compare output data from a key derivation operation to an expected value.
- *
-@@ -4180,10 +4182,12 @@ psa_status_t psa_generate_key(const psa_key_attributes_t *attributes,
- * It is implementation-dependent whether a failure to initialize
- * results in this error code.
- */
-+#ifndef __cplusplus
- psa_status_t psa_generate_key_ext(const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params,
- size_t params_data_length,
- mbedtls_svc_key_id_t *key);
-+#endif
-
- /**@}*/
-
-diff --git a/thirdparty/mbedtls/include/psa/crypto_struct.h b/thirdparty/mbedtls/include/psa/crypto_struct.h
-index 3913551aa8..e2c227b2eb 100644
---- a/thirdparty/mbedtls/include/psa/crypto_struct.h
-+++ b/thirdparty/mbedtls/include/psa/crypto_struct.h
-@@ -223,11 +223,13 @@ static inline struct psa_key_derivation_s psa_key_derivation_operation_init(
- return v;
- }
-
-+#ifndef __cplusplus
- struct psa_key_production_parameters_s {
- /* Future versions may add other fields in this structure. */
- uint32_t flags;
- uint8_t data[];
- };
-+#endif
-
- /** The default production parameters for key generation or key derivation.
- *
-diff --git a/thirdparty/mbedtls/include/psa/crypto_types.h b/thirdparty/mbedtls/include/psa/crypto_types.h
-index c21bad86cc..a36b6ee65d 100644
---- a/thirdparty/mbedtls/include/psa/crypto_types.h
-+++ b/thirdparty/mbedtls/include/psa/crypto_types.h
-@@ -477,7 +477,9 @@ typedef uint16_t psa_key_derivation_step_t;
- * - Other key types: reserved for future use. \c flags must be 0.
- *
- */
-+#ifndef __cplusplus
- typedef struct psa_key_production_parameters_s psa_key_production_parameters_t;
-+#endif
-
- /**@}*/
-
-diff --git a/thirdparty/mbedtls/library/psa_crypto_core.h b/thirdparty/mbedtls/library/psa_crypto_core.h
-index 9462d2e8be..c059162efe 100644
---- a/thirdparty/mbedtls/library/psa_crypto_core.h
-+++ b/thirdparty/mbedtls/library/psa_crypto_core.h
-@@ -351,9 +351,11 @@ psa_status_t psa_export_public_key_internal(
- * \param[in] params The key production parameters to check.
- * \param params_data_length Size of `params->data` in bytes.
- */
-+#ifndef __cplusplus
- int psa_key_production_parameters_are_default(
- const psa_key_production_parameters_t *params,
- size_t params_data_length);
-+#endif
-
- /**
- * \brief Generate a key.
-@@ -378,12 +380,14 @@ int psa_key_production_parameters_are_default(
- * \retval #PSA_ERROR_BUFFER_TOO_SMALL
- * The size of \p key_buffer is too small.
- */
-+#ifndef __cplusplus
- psa_status_t psa_generate_key_internal(const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params,
- size_t params_data_length,
- uint8_t *key_buffer,
- size_t key_buffer_size,
- size_t *key_buffer_length);
-+#endif
-
- /** Sign a message with a private key. For hash-and-sign algorithms,
- * this includes the hashing step.
-diff --git a/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h b/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
-index ea6aee32eb..6919971aca 100644
---- a/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
-+++ b/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h
-@@ -728,6 +728,7 @@ static inline psa_status_t psa_driver_wrapper_get_key_buffer_size_from_key_data(
- }
- }
-
-+#ifndef __cplusplus
- static inline psa_status_t psa_driver_wrapper_generate_key(
- const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params, size_t params_data_length,
-@@ -832,6 +833,7 @@ static inline psa_status_t psa_driver_wrapper_generate_key(
-
- return( status );
- }
-+#endif
-
- static inline psa_status_t psa_driver_wrapper_import_key(
- const psa_key_attributes_t *attributes,
-diff --git a/thirdparty/mbedtls/library/psa_crypto_rsa.h b/thirdparty/mbedtls/library/psa_crypto_rsa.h
-index ffeef26be1..6d695ddf50 100644
---- a/thirdparty/mbedtls/library/psa_crypto_rsa.h
-+++ b/thirdparty/mbedtls/library/psa_crypto_rsa.h
-@@ -130,10 +130,12 @@ psa_status_t mbedtls_psa_rsa_export_public_key(
- * \retval #PSA_ERROR_BUFFER_TOO_SMALL
- * The size of \p key_buffer is too small.
- */
-+#ifndef __cplusplus
- psa_status_t mbedtls_psa_rsa_generate_key(
- const psa_key_attributes_t *attributes,
- const psa_key_production_parameters_t *params, size_t params_data_length,
- uint8_t *key_buffer, size_t key_buffer_size, size_t *key_buffer_length);
-+#endif
-
- /** Sign an already-calculated hash with an RSA private key.
- *
diff --git a/thirdparty/misc/patches/qoa-min-fix.patch b/thirdparty/misc/patches/qoa-min-fix.patch
index 38303a1521..6008b5f8bc 100644
--- a/thirdparty/misc/patches/qoa-min-fix.patch
+++ b/thirdparty/misc/patches/qoa-min-fix.patch
@@ -1,5 +1,5 @@
diff --git a/qoa.h b/qoa.h
-index 592082933a..c890b88bd6 100644
+index cfed266bef..23612bb0bf 100644
--- a/qoa.h
+++ b/qoa.h
@@ -140,14 +140,14 @@ typedef struct {
@@ -24,19 +24,15 @@ index 592082933a..c890b88bd6 100644
#ifndef QOA_NO_STDIO
-@@ -394,9 +394,9 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
- #ifdef QOA_RECORD_TOTAL_ERROR
+@@ -395,7 +395,7 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
qoa_uint64_t best_error = -1;
#endif
-- qoa_uint64_t best_slice;
+ qoa_uint64_t best_slice = 0;
- qoa_lms_t best_lms;
-- int best_scalefactor;
-+ qoa_uint64_t best_slice = -1;
-+ qoa_lms_t best_lms = {{-1, -1, -1, -1}, {-1, -1, -1, -1}};
-+ int best_scalefactor = -1;
++ qoa_lms_t best_lms = {};
+ int best_scalefactor = 0;
for (int sfi = 0; sfi < 16; sfi++) {
- /* There is a strong correlation between the scalefactors of
@@ -500,7 +500,7 @@ void *qoa_encode(const short *sample_data, qoa_desc *qoa, unsigned int *out_len)
num_frames * QOA_LMS_LEN * 4 * qoa->channels + /* 4 * 4 bytes lms state per channel */
num_slices * 8 * qoa->channels; /* 8 byte slices */
diff --git a/thirdparty/misc/qoa.h b/thirdparty/misc/qoa.h
index c890b88bd6..23612bb0bf 100644
--- a/thirdparty/misc/qoa.h
+++ b/thirdparty/misc/qoa.h
@@ -394,9 +394,9 @@ unsigned int qoa_encode_frame(const short *sample_data, qoa_desc *qoa, unsigned
#ifdef QOA_RECORD_TOTAL_ERROR
qoa_uint64_t best_error = -1;
#endif
- qoa_uint64_t best_slice = -1;
- qoa_lms_t best_lms = {{-1, -1, -1, -1}, {-1, -1, -1, -1}};
- int best_scalefactor = -1;
+ qoa_uint64_t best_slice = 0;
+ qoa_lms_t best_lms = {};
+ int best_scalefactor = 0;
for (int sfi = 0; sfi < 16; sfi++) {
/* There is a strong correlation between the scalefactors of
diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h
index 8c185ccbca..02fec07448 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.2"
+#define THORVG_VERSION_STRING "0.14.8"
#endif
diff --git a/thirdparty/thorvg/inc/thorvg.h b/thirdparty/thorvg/inc/thorvg.h
index 47414d851a..4303092a5e 100644
--- a/thirdparty/thorvg/inc/thorvg.h
+++ b/thirdparty/thorvg/inc/thorvg.h
@@ -157,7 +157,7 @@ enum class FillRule
enum class CompositeMethod
{
None = 0, ///< No composition is applied.
- ClipPath, ///< The intersection of the source and the target is determined and only the resulting pixels from the source are rendered.
+ ClipPath, ///< The intersection of the source and the target is determined and only the resulting pixels from the source are rendered. Note that ClipPath only supports the Shape type.
AlphaMask, ///< Alpha Masking using the compositing target's pixels as an alpha value.
InvAlphaMask, ///< Alpha Masking using the complement to the compositing target's pixels as an alpha value.
LumaMask, ///< Alpha Masking using the grayscale (0.2125R + 0.7154G + 0.0721*B) of the compositing target's pixels. @since 0.9
@@ -165,7 +165,9 @@ enum class CompositeMethod
AddMask, ///< Combines the target and source objects pixels using target alpha. (T * TA) + (S * (255 - TA)) (Experimental API)
SubtractMask, ///< Subtracts the source color from the target color while considering their respective target alpha. (T * TA) - (S * (255 - TA)) (Experimental API)
IntersectMask, ///< Computes the result by taking the minimum value between the target alpha and the source alpha and multiplies it with the target color. (T * min(TA, SA)) (Experimental API)
- DifferenceMask ///< Calculates the absolute difference between the target color and the source color multiplied by the complement of the target alpha. abs(T - S * (255 - TA)) (Experimental API)
+ DifferenceMask, ///< Calculates the absolute difference between the target color and the source color multiplied by the complement of the target alpha. abs(T - S * (255 - TA)) (Experimental API)
+ LightenMask, ///< Where multiple masks intersect, the highest transparency value is used. (Experimental API)
+ DarkenMask ///< Where multiple masks intersect, the lowest transparency value is used. (Experimental API)
};
@@ -233,34 +235,6 @@ struct Matrix
/**
- * @brief A data structure representing a texture mesh vertex
- *
- * @param pt The vertex coordinate
- * @param uv The normalized texture coordinate in the range (0.0..1.0, 0.0..1.0)
- *
- * @note Experimental API
- */
-struct Vertex
-{
- Point pt;
- Point uv;
-};
-
-
-/**
- * @brief A data structure representing a triange in a texture mesh
- *
- * @param vertex The three vertices that make up the polygon
- *
- * @note Experimental API
- */
-struct Polygon
-{
- Vertex vertex[3];
-};
-
-
-/**
* @class Paint
*
* @brief An abstract class for managing graphical elements.
@@ -361,7 +335,7 @@ public:
*
* @note Experimental API
*/
- Result blend(BlendMethod method) const noexcept;
+ Result blend(BlendMethod method) noexcept;
/**
* @deprecated Use bounds(float* x, float* y, float* w, float* h, bool transformed) instead
@@ -371,15 +345,16 @@ public:
/**
* @brief Gets the axis-aligned bounding box of the paint object.
*
- * In case @p transform is @c true, all object's transformations are applied first, and then the bounding box is established. Otherwise, the bounding box is determined before any transformations.
- *
- * @param[out] x The x coordinate of the upper left corner of the object.
- * @param[out] y The y coordinate of the upper left corner of the object.
+ * @param[out] x The x-coordinate of the upper-left corner of the object.
+ * @param[out] y The y-coordinate of the upper-left corner of the object.
* @param[out] w The width of the object.
* @param[out] h The height of the object.
- * @param[in] transformed If @c true, the paint's transformations are taken into account, otherwise they aren't.
+ * @param[in] transformed If @c true, the paint's transformations are taken into account in the scene it belongs to. Otherwise they aren't.
*
+ * @note This is useful when you need to figure out the bounding box of the paint in the canvas space.
* @note The bounding box doesn't indicate the actual drawing region. It's the smallest rectangle that encloses the object.
+ * @note If @p transformed is @c true, the paint needs to be pushed into a canvas and updated before this api is called.
+ * @see Canvas::update()
*/
Result bounds(float* x, float* y, float* w, float* h, bool transformed) const noexcept;
@@ -411,9 +386,9 @@ public:
CompositeMethod composite(const Paint** target) const noexcept;
/**
- * @brief Gets the blending method of the object.
+ * @brief Retrieves the current blending method applied to the paint object.
*
- * @return The blending method
+ * @return The currently set blending method.
*
* @note Experimental API
*/
@@ -428,6 +403,15 @@ public:
*/
uint32_t identifier() const noexcept;
+ /**
+ * @brief Unique ID of this instance.
+ *
+ * This is reserved to specify an paint instance in a scene.
+ *
+ * @since Experimental API
+ */
+ uint32_t id = 0;
+
_TVG_DECLARE_PRIVATE(Paint);
};
@@ -675,7 +659,8 @@ public:
* @param[in] x2 The horizontal coordinate of the second point used to determine the gradient bounds.
* @param[in] y2 The vertical coordinate of the second point used to determine the gradient bounds.
*
- * @note In case the first and the second points are equal, an object filled with such a gradient fill is not rendered.
+ * @note In case the first and the second points are equal, an object is filled with a single color using the last color specified in the colorStops().
+ * @see Fill::colorStops()
*/
Result linear(float x1, float y1, float x2, float y2) noexcept;
@@ -734,6 +719,8 @@ public:
* @param[in] radius The radius of the bounding circle.
*
* @retval Result::InvalidArguments in case the @p radius value is zero or less.
+ *
+ * @note In case the @p radius is zero, an object is filled with a single color using the last color specified in the colorStops().
*/
Result radial(float cx, float cy, float radius) noexcept;
@@ -990,7 +977,7 @@ public:
/**
* @brief Sets the trim of the stroke along the defined path segment, allowing control over which part of the stroke is visible.
*
- * The values of the arguments @p begin, @p end, and @p offset are in the range of 0.0 to 1.0, representing the beginning of the path and the end, respectively.
+ * If the values of the arguments @p begin and @p end exceed the 0-1 range, they are wrapped around in a manner similar to angle wrapping, effectively treating the range as circular.
*
* @param[in] begin Specifies the start of the segment to display along the path.
* @param[in] end Specifies the end of the segment to display along the path.
@@ -1076,7 +1063,6 @@ public:
* @param[out] b The blue color channel value in the range [0 ~ 255].
* @param[out] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque.
*
- * @return Result::Success when succeed.
*/
Result fillColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a = nullptr) const noexcept;
@@ -1219,7 +1205,7 @@ public:
* when the @p copy has @c false. This means that loading the same data again will not result in duplicate operations
* for the sharable @p data. Instead, ThorVG will reuse the previously loaded picture data.
*
- * @param[in] data A pointer to a memory location where the content of the picture file is stored.
+ * @param[in] data A pointer to a memory location where the content of the picture file is stored. A null-terminated string is expected for non-binary data if @p copy is @c false.
* @param[in] size The size in bytes of the memory occupied by the @p data.
* @param[in] mimeType Mimetype or extension of data such as "jpg", "jpeg", "lottie", "svg", "svg+xml", "png", etc. In case an empty string or an unknown type is provided, the loaders will be tried one by one.
* @param[in] copy If @c true the data are copied into the engine local buffer, otherwise they are not.
@@ -1256,7 +1242,7 @@ public:
Result size(float* w, float* h) const noexcept;
/**
- * @brief Loads a raw data from a memory block with a given size.
+ * @brief Loads raw data in ARGB8888 format from a memory block of the given size.
*
* ThorVG efficiently caches the loaded data using the specified @p data address as a key
* when the @p copy has @c false. This means that loading the same data again will not result in duplicate operations
@@ -1265,47 +1251,27 @@ public:
* @param[in] data A pointer to a memory location where the content of the picture raw data is stored.
* @param[in] w The width of the image @p data in pixels.
* @param[in] h The height of the image @p data in pixels.
- * @param[in] premultiplied If @c true, the given image data is alpha-premultiplied.
* @param[in] copy If @c true the data are copied into the engine local buffer, otherwise they are not.
*
+ * @note It expects premultiplied alpha data.
* @since 0.9
*/
Result load(uint32_t* data, uint32_t w, uint32_t h, bool copy) noexcept;
/**
- * @brief Sets or removes the triangle mesh to deform the image.
- *
- * If a mesh is provided, the transform property of the Picture will apply to the triangle mesh, and the
- * image data will be used as the texture.
- *
- * If @p triangles is @c nullptr, or @p triangleCnt is 0, the mesh will be removed.
- *
- * Only raster image types are supported at this time (png, jpg). Vector types like svg and tvg do not support.
- * mesh deformation. However, if required you should be able to render a vector image to a raster image and then apply a mesh.
+ * @brief Retrieve a paint object from the Picture scene by its Unique ID.
*
- * @param[in] triangles An array of Polygons(triangles) that make up the mesh, or null to remove the mesh.
- * @param[in] triangleCnt The number of Polygons(triangles) provided, or 0 to remove the mesh.
+ * This function searches for a paint object within the Picture scene that matches the provided @p id.
*
- * @note The Polygons are copied internally, so modifying them after calling Mesh::mesh has no affect.
- * @warning Please do not use it, this API is not official one. It could be modified in the next version.
+ * @param[in] id The Unique ID of the paint object.
*
- * @note Experimental API
- */
- Result mesh(const Polygon* triangles, uint32_t triangleCnt) noexcept;
-
- /**
- * @brief Return the number of triangles in the mesh, and optionally get a pointer to the array of triangles in the mesh.
+ * @return A pointer to the paint object that matches the given identifier, or @c nullptr if no matching paint object is found.
*
- * @param[out] triangles Optional. A pointer to the array of Polygons used by this mesh.
- *
- * @return The number of polygons in the array.
- *
- * @note Modifying the triangles returned by this method will modify them directly within the mesh.
- * @warning Please do not use it, this API is not official one. It could be modified in the next version.
+ * @see Accessor::id()
*
* @note Experimental API
*/
- uint32_t mesh(const Polygon** triangles) const noexcept;
+ const Paint* paint(uint32_t id) noexcept;
/**
* @brief Creates a new Picture object.
@@ -1454,8 +1420,6 @@ public:
* @param[in] g The green color channel value in the range [0 ~ 255]. The default value is 0.
* @param[in] b The blue color channel value in the range [0 ~ 255]. The default value is 0.
*
- * @retval Result::InsufficientCondition when the font has not been set up prior to this operation.
- *
* @see Text::font()
*
* @note Experimental API
@@ -1469,8 +1433,6 @@ public:
*
* @param[in] f The unique pointer to the gradient fill.
*
- * @retval Result::InsufficientCondition when the font has not been set up prior to this operation.
- *
* @note Either a solid color or a gradient fill is applied, depending on what was set as last.
* @note Experimental API
*
@@ -1781,6 +1743,19 @@ public:
*/
static Result term(CanvasEngine engine) noexcept;
+ /**
+ * @brief Retrieves the version of the TVG engine.
+ *
+ * @param[out] major A major version number.
+ * @param[out] minor A minor version number.
+ * @param[out] micro A micro version number.
+ *
+ * @return The version of the engine in the format major.minor.micro, or a @p nullptr in case of an internal error.
+ *
+ * @note Experimental API
+ */
+ static const char* version(uint32_t* major, uint32_t* minor, uint32_t* micro) noexcept;
+
_TVG_DISABLE_CTOR(Initializer);
};
@@ -1879,7 +1854,7 @@ public:
* @retval Result::InsufficientCondition In case the animation is not loaded.
* @retval Result::NonSupport When it's not animatable.
*
- * @note Range from 0.0~1.0
+ * @note Animation allows a range from 0.0 to 1.0. @p end should not be higher than @p begin.
* @note If a marker has been specified, its range will be disregarded.
* @see LottieAnimation::segment(const char* marker)
* @note Experimental API
@@ -2030,17 +2005,36 @@ class TVG_API Accessor final
public:
~Accessor();
+ TVG_DEPRECATED std::unique_ptr<Picture> set(std::unique_ptr<Picture> picture, std::function<bool(const Paint* paint)> func) noexcept;
+
/**
* @brief Set the access function for traversing the Picture scene tree nodes.
*
* @param[in] picture The picture node to traverse the internal scene-tree.
* @param[in] func The callback function calling for every paint nodes of the Picture.
- *
- * @return Return the given @p picture instance.
+ * @param[in] data Data passed to the @p func as its argument.
*
* @note The bitmap based picture might not have the scene-tree.
+ *
+ * @note Experimental API
+ */
+ Result set(const Picture* picture, std::function<bool(const Paint* paint, void* data)> func, void* data) noexcept;
+
+ /**
+ * @brief Generate a unique ID (hash key) from a given name.
+ *
+ * This function computes a unique identifier value based on the provided string.
+ * You can use this to assign a unique ID to the Paint object.
+ *
+ * @param[in] name The input string to generate the unique identifier from.
+ *
+ * @return The generated unique identifier value.
+ *
+ * @see Paint::id
+ *
+ * @note Experimental API
*/
- std::unique_ptr<Picture> set(std::unique_ptr<Picture> picture, std::function<bool(const Paint* paint)> func) noexcept;
+ static uint32_t id(const char* name) noexcept;
/**
* @brief Creates a new Accessor object.
diff --git a/thirdparty/thorvg/patches/pr2702-sw_engine-handle-small-cubics.patch b/thirdparty/thorvg/patches/pr2702-sw_engine-handle-small-cubics.patch
new file mode 100644
index 0000000000..69f4a5cf85
--- /dev/null
+++ b/thirdparty/thorvg/patches/pr2702-sw_engine-handle-small-cubics.patch
@@ -0,0 +1,96 @@
+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/src/common/tvgArray.h b/thirdparty/thorvg/src/common/tvgArray.h
index 8178bd0e42..19c8f69726 100644
--- a/thirdparty/thorvg/src/common/tvgArray.h
+++ b/thirdparty/thorvg/src/common/tvgArray.h
@@ -59,7 +59,7 @@ struct Array
data[count++] = element;
}
- void push(Array<T>& rhs)
+ void push(const Array<T>& rhs)
{
if (rhs.count == 0) return;
grow(rhs.count);
diff --git a/thirdparty/thorvg/src/common/tvgCompressor.cpp b/thirdparty/thorvg/src/common/tvgCompressor.cpp
index b61718f9a7..aebe9a4ef1 100644
--- a/thirdparty/thorvg/src/common/tvgCompressor.cpp
+++ b/thirdparty/thorvg/src/common/tvgCompressor.cpp
@@ -478,6 +478,8 @@ size_t b64Decode(const char* encoded, const size_t len, char** decoded)
unsigned long djb2Encode(const char* str)
{
+ if (!str) return 0;
+
unsigned long hash = 5381;
int c;
diff --git a/thirdparty/thorvg/src/common/tvgInlist.h b/thirdparty/thorvg/src/common/tvgInlist.h
index ff28cfd48e..fc99ae3d14 100644
--- a/thirdparty/thorvg/src/common/tvgInlist.h
+++ b/thirdparty/thorvg/src/common/tvgInlist.h
@@ -100,7 +100,7 @@ struct Inlist
if (element == tail) tail = element->prev;
}
- bool empty()
+ bool empty() const
{
return head ? false : true;
}
diff --git a/thirdparty/thorvg/src/common/tvgLines.cpp b/thirdparty/thorvg/src/common/tvgLines.cpp
index 9d704900a5..49d992f127 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) < BEZIER_EPSILON) {
+ if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < 1e-3f) {
break;
}
if (length < at) {
diff --git a/thirdparty/thorvg/src/common/tvgLock.h b/thirdparty/thorvg/src/common/tvgLock.h
index 59f68d0d44..d3a4e41c5c 100644
--- a/thirdparty/thorvg/src/common/tvgLock.h
+++ b/thirdparty/thorvg/src/common/tvgLock.h
@@ -25,8 +25,6 @@
#ifdef THORVG_THREAD_SUPPORT
-#define _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR
-
#include <mutex>
#include "tvgTaskScheduler.h"
diff --git a/thirdparty/thorvg/src/common/tvgMath.cpp b/thirdparty/thorvg/src/common/tvgMath.cpp
index c56b32249f..0254cce9b8 100644
--- a/thirdparty/thorvg/src/common/tvgMath.cpp
+++ b/thirdparty/thorvg/src/common/tvgMath.cpp
@@ -133,3 +133,11 @@ Point operator*(const Point& pt, const Matrix& m)
auto ty = pt.x * m.e21 + pt.y * m.e22 + m.e23;
return {tx, ty};
}
+
+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;
+ return static_cast<uint8_t>(result);
+}
diff --git a/thirdparty/thorvg/src/common/tvgMath.h b/thirdparty/thorvg/src/common/tvgMath.h
index 668260c689..df39e3b9af 100644
--- a/thirdparty/thorvg/src/common/tvgMath.h
+++ b/thirdparty/thorvg/src/common/tvgMath.h
@@ -78,17 +78,17 @@ bool mathIdentity(const Matrix* m);
Matrix operator*(const Matrix& lhs, const Matrix& rhs);
bool operator==(const Matrix& lhs, const Matrix& rhs);
-static inline bool mathRightAngle(const Matrix* m)
+static inline bool mathRightAngle(const Matrix& m)
{
- auto radian = fabsf(mathAtan2(m->e21, m->e11));
+ auto radian = fabsf(mathAtan2(m.e21, m.e11));
if (radian < FLOAT_EPSILON || mathEqual(radian, MATH_PI2) || mathEqual(radian, MATH_PI)) return true;
return false;
}
-static inline bool mathSkewed(const Matrix* m)
+static inline bool mathSkewed(const Matrix& m)
{
- return !mathZero(m->e21 + m->e12);
+ return !mathZero(m.e21 + m.e12);
}
@@ -233,6 +233,17 @@ static inline Point operator/(const Point& lhs, const float rhs)
}
+static inline Point mathNormal(const Point& p1, const Point& p2)
+{
+ auto dir = p2 - p1;
+ auto len = mathLength(dir);
+ if (mathZero(len)) return {};
+
+ auto unitDir = dir / len;
+ return {-unitDir.y, unitDir.x};
+}
+
+
static inline void mathLog(const Point& pt)
{
TVGLOG("COMMON", "Point: [%f %f]", pt.x, pt.y);
@@ -248,5 +259,6 @@ static inline T mathLerp(const T &start, const T &end, float t)
return static_cast<T>(start + (end - start) * t);
}
+uint8_t mathLerp(const uint8_t &start, const uint8_t &end, float t);
#endif //_TVG_MATH_H_
diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
index 8fbf3816ea..5aab4f1b0d 100644
--- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
+++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp
@@ -588,6 +588,11 @@ static bool _hslToRgb(float hue, float saturation, float brightness, uint8_t* re
float _red = 0, _green = 0, _blue = 0;
uint32_t i = 0;
+ while (hue < 0) hue += 360.0f;
+ hue = fmod(hue, 360.0f);
+ saturation = saturation > 0 ? std::min(saturation, 1.0f) : 0.0f;
+ brightness = brightness > 0 ? std::min(brightness, 1.0f) : 0.0f;
+
if (mathZero(saturation)) _red = _green = _blue = brightness;
else {
if (mathEqual(hue, 360.0)) hue = 0.0f;
@@ -710,15 +715,15 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
return true;
} else if (len >= 10 && (str[0] == 'h' || str[0] == 'H') && (str[1] == 's' || str[1] == 'S') && (str[2] == 'l' || str[2] == 'L') && str[3] == '(' && str[len - 1] == ')') {
float th, ts, tb;
- const char *content, *hue, *saturation, *brightness;
- content = str + 4;
- content = _skipSpace(content, nullptr);
+ const char* content = _skipSpace(str + 4, nullptr);
+ const char* hue = nullptr;
if (_parseNumber(&content, &hue, &th) && hue) {
- th = float(uint32_t(th) % 360);
+ const char* saturation = nullptr;
hue = _skipSpace(hue, nullptr);
hue = (char*)_skipComma(hue);
hue = _skipSpace(hue, nullptr);
if (_parseNumber(&hue, &saturation, &ts) && saturation && *saturation == '%') {
+ const char* brightness = nullptr;
ts /= 100.0f;
saturation = _skipSpace(saturation + 1, nullptr);
saturation = (char*)_skipComma(saturation);
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
index 05cbdc7f3a..09b75d370b 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
@@ -134,7 +134,6 @@ struct SwFill
{
struct SwLinear {
float dx, dy;
- float len;
float offset;
};
@@ -154,6 +153,7 @@ struct SwFill
uint32_t* ctable;
FillSpread spread;
+ bool solid = false; //solid color fill with the last color from colorStops
bool translucent;
};
@@ -301,8 +301,8 @@ static inline uint32_t JOIN(uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3)
static inline uint32_t ALPHA_BLEND(uint32_t c, uint32_t a)
{
- return (((((c >> 8) & 0x00ff00ff) * a + 0x00ff00ff) & 0xff00ff00) +
- ((((c & 0x00ff00ff) * a + 0x00ff00ff) >> 8) & 0x00ff00ff));
+ ++a;
+ return (((((c >> 8) & 0x00ff00ff) * a) & 0xff00ff00) + ((((c & 0x00ff00ff) * a) >> 8) & 0x00ff00ff));
}
static inline uint32_t INTERPOLATE(uint32_t s, uint32_t d, uint8_t a)
@@ -367,7 +367,7 @@ static inline uint32_t opBlendSrcOver(uint32_t s, TVG_UNUSED uint32_t d, TVG_UNU
}
//TODO: BlendMethod could remove the alpha parameter.
-static inline uint32_t opBlendDifference(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
+static inline uint32_t opBlendDifference(uint32_t s, uint32_t d, uint8_t a)
{
//if (s > d) => s - d
//else => d - s
@@ -404,8 +404,7 @@ static inline uint32_t opBlendScreen(uint32_t s, uint32_t d, TVG_UNUSED uint8_t
return JOIN(255, c1, c2, c3);
}
-
-static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
+static inline uint32_t opBlendDirectMultiply(uint32_t s, uint32_t d, uint8_t a)
{
// s * d
auto c1 = MULTIPLY(C1(s), C1(d));
@@ -414,6 +413,10 @@ static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, TVG_UNUSED uint8_
return JOIN(255, c1, c2, c3);
}
+static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, uint8_t a)
+{
+ return opBlendDirectMultiply(s, d, a) + ALPHA_BLEND(d, IA(s));
+}
static inline uint32_t opBlendOverlay(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
@@ -492,40 +495,42 @@ 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);
+SwPoint mathTransform(const Point* to, const Matrix& transform);
bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack);
bool mathClipBBox(const SwBBox& clipper, SwBBox& clipee);
void shapeReset(SwShape* shape);
-bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite);
+bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite);
bool shapePrepared(const SwShape* shape);
bool shapeGenRle(SwShape* shape, const RenderShape* rshape, bool antiAlias);
void shapeDelOutline(SwShape* shape, SwMpool* mpool, uint32_t tid);
-void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix* transform);
-bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid);
+void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix& transform);
+bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid);
void shapeFree(SwShape* shape);
void shapeDelStroke(SwShape* shape);
-bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable);
-bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable);
+bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix& transform, SwSurface* surface, uint8_t opacity, bool ctable);
+bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix& transform, SwSurface* surface, uint8_t opacity, bool ctable);
void shapeResetFill(SwShape* shape);
void shapeResetStrokeFill(SwShape* shape);
void shapeDelFill(SwShape* shape);
void shapeDelStrokeFill(SwShape* shape);
-void strokeReset(SwStroke* stroke, const RenderShape* shape, const Matrix* transform);
+void strokeReset(SwStroke* stroke, const RenderShape* shape, const Matrix& transform);
bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline);
SwOutline* strokeExportOutline(SwStroke* stroke, SwMpool* mpool, unsigned tid);
void strokeFree(SwStroke* stroke);
-bool imagePrepare(SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid);
+bool imagePrepare(SwImage* image, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid);
bool imageGenRle(SwImage* image, const SwBBox& renderRegion, bool antiAlias);
void imageDelOutline(SwImage* image, SwMpool* mpool, uint32_t tid);
void imageReset(SwImage* image);
void imageFree(SwImage* image);
-bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable);
+bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix& transform, SwSurface* surface, uint8_t opacity, bool ctable);
+const Fill::ColorStop* fillFetchSolid(const SwFill* fill, const Fill* fdata);
void fillReset(SwFill* fill);
void fillFree(SwFill* fill);
@@ -561,11 +566,11 @@ SwOutline* mpoolReqDashOutline(SwMpool* mpool, unsigned idx);
void mpoolRetDashOutline(SwMpool* mpool, unsigned idx);
bool rasterCompositor(SwSurface* surface);
-bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id);
+bool rasterGradientShape(SwSurface* surface, SwShape* shape, const Fill* fdata, uint8_t opacity);
bool rasterShape(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
-bool rasterImage(SwSurface* surface, SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox& bbox, uint8_t opacity);
+bool rasterImage(SwSurface* surface, SwImage* image, const Matrix& transform, const SwBBox& bbox, uint8_t opacity);
bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
-bool rasterGradientStroke(SwSurface* surface, SwShape* shape, unsigned id);
+bool rasterGradientStroke(SwSurface* surface, SwShape* shape, const Fill* fdata, uint8_t opacity);
bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h);
void rasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len);
void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len);
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp
index bd0b5ffdcb..631294ad40 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp
@@ -58,7 +58,7 @@ static void _calculateCoefficients(const SwFill* fill, uint32_t x, uint32_t y, f
auto deltaDeltaRr = 2.0f * (radial->a11 * radial->a11 + radial->a21 * radial->a21) * radial->invA;
det = b * b + (rr - radial->fr * radial->fr) * radial->invA;
- deltaDet = 2.0f * b * deltaB + deltaB * deltaB + deltaRr + deltaDeltaRr;
+ deltaDet = 2.0f * b * deltaB + deltaB * deltaB + deltaRr + deltaDeltaRr * 0.5f;
deltaDeltaDet = 2.0f * deltaB * deltaB + deltaDeltaRr;
}
@@ -125,6 +125,8 @@ static void _applyAA(const SwFill* fill, uint32_t begin, uint32_t end)
static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* surface, uint8_t opacity)
{
+ if (fill->solid) return true;
+
if (!fill->ctable) {
fill->ctable = static_cast<uint32_t*>(malloc(GRADIENT_STOP_SIZE * sizeof(uint32_t)));
if (!fill->ctable) return false;
@@ -205,28 +207,33 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface*
}
-bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix* transform)
+bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix& transform)
{
float x1, x2, y1, y2;
if (linear->linear(&x1, &y1, &x2, &y2) != Result::Success) return false;
fill->linear.dx = x2 - x1;
fill->linear.dy = y2 - y1;
- fill->linear.len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy;
+ auto len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy;
- if (fill->linear.len < FLOAT_EPSILON) return true;
+ if (len < FLOAT_EPSILON) {
+ if (mathZero(fill->linear.dx) && mathZero(fill->linear.dy)) {
+ fill->solid = true;
+ }
+ return true;
+ }
- fill->linear.dx /= fill->linear.len;
- fill->linear.dy /= fill->linear.len;
+ fill->linear.dx /= len;
+ fill->linear.dy /= len;
fill->linear.offset = -fill->linear.dx * x1 - fill->linear.dy * y1;
auto gradTransform = linear->transform();
bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform));
if (isTransformation) {
- if (transform) gradTransform = *transform * gradTransform;
- } else if (transform) {
- gradTransform = *transform;
+ gradTransform = transform * gradTransform;
+ } else {
+ gradTransform = transform;
isTransformation = true;
}
@@ -239,15 +246,13 @@ bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix* tr
auto dx = fill->linear.dx;
fill->linear.dx = dx * invTransform.e11 + fill->linear.dy * invTransform.e21;
fill->linear.dy = dx * invTransform.e12 + fill->linear.dy * invTransform.e22;
-
- fill->linear.len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy;
}
return true;
}
-bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* transform)
+bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix& transform)
{
auto cx = P(radial)->cx;
auto cy = P(radial)->cy;
@@ -256,7 +261,10 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* tr
auto fy = P(radial)->fy;
auto fr = P(radial)->fr;
- if (r < FLOAT_EPSILON) return true;
+ if (mathZero(r)) {
+ fill->solid = true;
+ return true;
+ }
fill->radial.dr = r - fr;
fill->radial.dx = cx - fx;
@@ -289,12 +297,10 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* tr
auto gradTransform = radial->transform();
bool isTransformation = !mathIdentity((const Matrix*)(&gradTransform));
- if (transform) {
- if (isTransformation) gradTransform = *transform * gradTransform;
- else {
- gradTransform = *transform;
- isTransformation = true;
- }
+ if (isTransformation) gradTransform = transform * gradTransform;
+ else {
+ gradTransform = transform;
+ isTransformation = true;
}
if (isTransformation) {
@@ -816,25 +822,32 @@ void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint3
}
-bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable)
+bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix& transform, SwSurface* surface, uint8_t opacity, bool ctable)
{
if (!fill) return false;
fill->spread = fdata->spread();
- if (ctable) {
- if (!_updateColorTable(fill, fdata, surface, opacity)) return false;
- }
-
if (fdata->identifier() == TVG_CLASS_ID_LINEAR) {
- return _prepareLinear(fill, static_cast<const LinearGradient*>(fdata), transform);
+ if (!_prepareLinear(fill, static_cast<const LinearGradient*>(fdata), transform)) return false;
} else if (fdata->identifier() == TVG_CLASS_ID_RADIAL) {
- return _prepareRadial(fill, static_cast<const RadialGradient*>(fdata), transform);
+ if (!_prepareRadial(fill, static_cast<const RadialGradient*>(fdata), transform)) return false;
}
- //LOG: What type of gradient?!
+ if (ctable) return _updateColorTable(fill, fdata, surface, opacity);
+ return true;
+}
+
+
+const Fill::ColorStop* fillFetchSolid(const SwFill* fill, const Fill* fdata)
+{
+ if (!fill->solid) return nullptr;
+
+ const Fill::ColorStop* colors;
+ auto cnt = fdata->colorStops(&colors);
+ if (cnt == 0 || !colors) return nullptr;
- return false;
+ return colors + cnt - 1;
}
@@ -845,6 +858,7 @@ void fillReset(SwFill* fill)
fill->ctable = nullptr;
}
fill->translucent = false;
+ fill->solid = false;
}
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwImage.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwImage.cpp
index e1d41a0d52..3fc64ce036 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwImage.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwImage.cpp
@@ -27,14 +27,14 @@
/* Internal Class Implementation */
/************************************************************************/
-static inline bool _onlyShifted(const Matrix* m)
+static inline bool _onlyShifted(const Matrix& m)
{
- if (mathEqual(m->e11, 1.0f) && mathEqual(m->e22, 1.0f) && mathZero(m->e12) && mathZero(m->e21)) return true;
+ if (mathEqual(m.e11, 1.0f) && mathEqual(m.e22, 1.0f) && mathZero(m.e12) && mathZero(m.e21)) return true;
return false;
}
-static bool _genOutline(SwImage* image, const RenderMesh* mesh, const Matrix* transform, SwMpool* mpool, unsigned tid)
+static bool _genOutline(SwImage* image, const Matrix& transform, SwMpool* mpool, unsigned tid)
{
image->outline = mpoolReqOutline(mpool, tid);
auto outline = image->outline;
@@ -45,48 +45,12 @@ static bool _genOutline(SwImage* image, const RenderMesh* mesh, const Matrix* tr
outline->closed.reserve(1);
Point to[4];
- if (mesh->triangleCnt > 0) {
- // TODO: Optimise me. We appear to calculate this exact min/max bounding area in multiple
- // places. We should be able to re-use one we have already done? Also see:
- // tvgPicture.h --> bounds
- // tvgSwRasterTexmap.h --> _rasterTexmapPolygonMesh
- //
- // TODO: Should we calculate the exact path(s) of the triangle mesh instead?
- // i.e. copy tvgSwShape.capp -> _genOutline?
- //
- // TODO: Cntrs?
- auto triangles = mesh->triangles;
- auto min = triangles[0].vertex[0].pt;
- auto max = triangles[0].vertex[0].pt;
-
- for (uint32_t i = 0; i < mesh->triangleCnt; ++i) {
- if (triangles[i].vertex[0].pt.x < min.x) min.x = triangles[i].vertex[0].pt.x;
- else if (triangles[i].vertex[0].pt.x > max.x) max.x = triangles[i].vertex[0].pt.x;
- if (triangles[i].vertex[0].pt.y < min.y) min.y = triangles[i].vertex[0].pt.y;
- else if (triangles[i].vertex[0].pt.y > max.y) max.y = triangles[i].vertex[0].pt.y;
-
- if (triangles[i].vertex[1].pt.x < min.x) min.x = triangles[i].vertex[1].pt.x;
- else if (triangles[i].vertex[1].pt.x > max.x) max.x = triangles[i].vertex[1].pt.x;
- if (triangles[i].vertex[1].pt.y < min.y) min.y = triangles[i].vertex[1].pt.y;
- else if (triangles[i].vertex[1].pt.y > max.y) max.y = triangles[i].vertex[1].pt.y;
-
- if (triangles[i].vertex[2].pt.x < min.x) min.x = triangles[i].vertex[2].pt.x;
- else if (triangles[i].vertex[2].pt.x > max.x) max.x = triangles[i].vertex[2].pt.x;
- if (triangles[i].vertex[2].pt.y < min.y) min.y = triangles[i].vertex[2].pt.y;
- else if (triangles[i].vertex[2].pt.y > max.y) max.y = triangles[i].vertex[2].pt.y;
- }
- to[0] = {min.x, min.y};
- to[1] = {max.x, min.y};
- to[2] = {max.x, max.y};
- to[3] = {min.x, max.y};
- } else {
- auto w = static_cast<float>(image->w);
- auto h = static_cast<float>(image->h);
- to[0] = {0, 0};
- to[1] = {w, 0};
- to[2] = {w, h};
- to[3] = {0, h};
- }
+ auto w = static_cast<float>(image->w);
+ auto h = static_cast<float>(image->h);
+ to[0] = {0, 0};
+ to[1] = {w, 0};
+ to[2] = {w, h};
+ to[3] = {0, h};
for (int i = 0; i < 4; i++) {
outline->pts.push(mathTransform(&to[i], transform));
@@ -108,25 +72,25 @@ static bool _genOutline(SwImage* image, const RenderMesh* mesh, const Matrix* tr
/* External Class Implementation */
/************************************************************************/
-bool imagePrepare(SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid)
+bool imagePrepare(SwImage* image, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid)
{
image->direct = _onlyShifted(transform);
//Fast track: Non-transformed image but just shifted.
if (image->direct) {
- image->ox = -static_cast<int32_t>(nearbyint(transform->e13));
- image->oy = -static_cast<int32_t>(nearbyint(transform->e23));
+ image->ox = -static_cast<int32_t>(nearbyint(transform.e13));
+ image->oy = -static_cast<int32_t>(nearbyint(transform.e23));
//Figure out the scale factor by transform matrix
} else {
- auto scaleX = sqrtf((transform->e11 * transform->e11) + (transform->e21 * transform->e21));
- auto scaleY = sqrtf((transform->e22 * transform->e22) + (transform->e12 * transform->e12));
+ auto scaleX = sqrtf((transform.e11 * transform.e11) + (transform.e21 * transform.e21));
+ auto scaleY = sqrtf((transform.e22 * transform.e22) + (transform.e12 * transform.e12));
image->scale = (fabsf(scaleX - scaleY) > 0.01f) ? 1.0f : scaleX;
- if (mathZero(transform->e12) && mathZero(transform->e21)) image->scaled = true;
+ if (mathZero(transform.e12) && mathZero(transform.e21)) image->scaled = true;
else image->scaled = false;
}
- if (!_genOutline(image, mesh, transform, mpool, tid)) return false;
+ if (!_genOutline(image, transform, mpool, tid)) return false;
return mathUpdateOutlineBBox(image->outline, clipRegion, renderRegion, image->direct);
}
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp
index ae158c836a..60dbbc4fbc 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp
+++ b/thirdparty/thorvg/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);
@@ -254,12 +259,10 @@ SwFixed mathDiff(SwFixed angle1, SwFixed angle2)
}
-SwPoint mathTransform(const Point* to, const Matrix* transform)
+SwPoint mathTransform(const Point* to, const Matrix& transform)
{
- if (!transform) return {TO_SWCOORD(to->x), TO_SWCOORD(to->y)};
-
- auto tx = to->x * transform->e11 + to->y * transform->e12 + transform->e13;
- auto ty = to->x * transform->e21 + to->y * transform->e22 + transform->e23;
+ auto tx = to->x * transform.e11 + to->y * transform.e12 + transform.e13;
+ auto ty = to->x * transform.e21 + to->y * transform.e22 + transform.e23;
return {TO_SWCOORD(tx), TO_SWCOORD(ty)};
}
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp
index 042d1e2b44..90d91e17e0 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp
@@ -194,10 +194,21 @@ static inline uint8_t _opMaskDifference(uint8_t s, uint8_t d, uint8_t a)
}
+static inline uint8_t _opMaskLighten(uint8_t s, uint8_t d, uint8_t a)
+{
+ return (s > d) ? s : d;
+}
+
+
+static inline uint8_t _opMaskDarken(uint8_t s, uint8_t d, uint8_t a)
+{
+ return (s < d) ? s : d;
+}
+
+
static inline bool _direct(CompositeMethod method)
{
- //subtract & Intersect allows the direct composition
- if (method == CompositeMethod::SubtractMask || method == CompositeMethod::IntersectMask) return true;
+ if (method == CompositeMethod::SubtractMask || method == CompositeMethod::IntersectMask || method == CompositeMethod::DarkenMask) return true;
return false;
}
@@ -209,6 +220,8 @@ static inline SwMask _getMaskOp(CompositeMethod method)
case CompositeMethod::SubtractMask: return _opMaskSubtract;
case CompositeMethod::DifferenceMask: return _opMaskDifference;
case CompositeMethod::IntersectMask: return _opMaskIntersect;
+ case CompositeMethod::LightenMask: return _opMaskLighten;
+ case CompositeMethod::DarkenMask: return _opMaskDarken;
default: return nullptr;
}
}
@@ -832,7 +845,7 @@ static bool _rasterScaledRleImage(SwSurface* surface, const SwImage* image, cons
}
-static bool _scaledRleImage(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox& region, uint8_t opacity)
+static bool _scaledRleImage(SwSurface* surface, const SwImage* image, const Matrix& transform, const SwBBox& region, uint8_t opacity)
{
if (surface->channelSize == sizeof(uint8_t)) {
TVGERR("SW_ENGINE", "Not supported scaled rle image!");
@@ -841,9 +854,7 @@ static bool _scaledRleImage(SwSurface* surface, const SwImage* image, const Matr
Matrix itransform;
- if (transform) {
- if (!mathInverse(transform, &itransform)) return false;
- } else mathIdentity(&itransform);
+ if (!mathInverse(&transform, &itransform)) return true;
if (_compositing(surface)) {
if (_matting(surface)) return _rasterScaledMattedRleImage(surface, image, &itransform, region, opacity);
@@ -1197,13 +1208,11 @@ static bool _rasterScaledImage(SwSurface* surface, const SwImage* image, const M
}
-static bool _scaledImage(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox& region, uint8_t opacity)
+static bool _scaledImage(SwSurface* surface, const SwImage* image, const Matrix& transform, const SwBBox& region, uint8_t opacity)
{
Matrix itransform;
- if (transform) {
- if (!mathInverse(transform, &itransform)) return false;
- } else mathIdentity(&itransform);
+ if (!mathInverse(&transform, &itransform)) return true;
if (_compositing(surface)) {
if (_matting(surface)) return _rasterScaledMattedImage(surface, image, &itransform, region, opacity);
@@ -1374,7 +1383,7 @@ static bool _rasterDirectBlendingImage(SwSurface* surface, const SwImage* image,
*dst = INTERPOLATE(tmp, *dst, A(*src));
}
} else {
- for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) {
+ for (auto x = region.min.x; x < region.max.x; x++, dst++, src++) {
auto tmp = ALPHA_BLEND(*src, opacity);
auto tmp2 = surface->blender(tmp, *dst, 255);
*dst = INTERPOLATE(tmp2, *dst, A(tmp));
@@ -1389,29 +1398,45 @@ static bool _rasterDirectBlendingImage(SwSurface* surface, const SwImage* image,
static bool _rasterDirectImage(SwSurface* surface, const SwImage* image, const SwBBox& region, uint8_t opacity)
{
- if (surface->channelSize == sizeof(uint8_t)) {
- TVGERR("SW_ENGINE", "Not supported grayscale image!");
- return false;
- }
-
- auto dbuffer = &surface->buf32[region.min.y * surface->stride + region.min.x];
auto sbuffer = image->buf32 + (region.min.y + image->oy) * image->stride + (region.min.x + image->ox);
- for (auto y = region.min.y; y < region.max.y; ++y) {
- auto dst = dbuffer;
- auto src = sbuffer;
- if (opacity == 255) {
- for (auto x = region.min.x; x < region.max.x; x++, dst++, src++) {
- *dst = *src + ALPHA_BLEND(*dst, IA(*src));
+ //32bits channels
+ if (surface->channelSize == sizeof(uint32_t)) {
+ auto dbuffer = &surface->buf32[region.min.y * surface->stride + region.min.x];
+
+ for (auto y = region.min.y; y < region.max.y; ++y) {
+ auto dst = dbuffer;
+ auto src = sbuffer;
+ if (opacity == 255) {
+ for (auto x = region.min.x; x < region.max.x; x++, dst++, src++) {
+ *dst = *src + ALPHA_BLEND(*dst, IA(*src));
+ }
+ } else {
+ for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) {
+ auto tmp = ALPHA_BLEND(*src, opacity);
+ *dst = tmp + ALPHA_BLEND(*dst, IA(tmp));
+ }
}
- } else {
- for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) {
- auto tmp = ALPHA_BLEND(*src, opacity);
- *dst = tmp + ALPHA_BLEND(*dst, IA(tmp));
+ dbuffer += surface->stride;
+ sbuffer += image->stride;
+ }
+ //8bits grayscale
+ } else if (surface->channelSize == sizeof(uint8_t)) {
+ auto dbuffer = &surface->buf8[region.min.y * surface->stride + region.min.x];
+
+ for (auto y = region.min.y; y < region.max.y; ++y, dbuffer += surface->stride, sbuffer += image->stride) {
+ auto dst = dbuffer;
+ auto src = sbuffer;
+ if (opacity == 255) {
+ for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) {
+ *dst = *src + MULTIPLY(*dst, ~*src);
+ }
+ } else {
+ for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) {
+ *dst = INTERPOLATE8(*src, *dst, opacity);
+ }
}
}
- dbuffer += surface->stride;
- sbuffer += image->stride;
}
return true;
}
@@ -1433,7 +1458,7 @@ static bool _directImage(SwSurface* surface, const SwImage* image, const SwBBox&
//Blenders for the following scenarios: [RLE / Whole] * [Direct / Scaled / Transformed]
-static bool _rasterImage(SwSurface* surface, SwImage* image, const Matrix* transform, const SwBBox& region, uint8_t opacity)
+static bool _rasterImage(SwSurface* surface, SwImage* image, const Matrix& transform, const SwBBox& region, uint8_t opacity)
{
//RLE Image
if (image->rle) {
@@ -1574,8 +1599,6 @@ static bool _rasterSolidGradientRect(SwSurface* surface, const SwBBox& region, c
static bool _rasterLinearGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill)
{
- if (fill->linear.len < FLOAT_EPSILON) return false;
-
if (_compositing(surface)) {
if (_matting(surface)) return _rasterGradientMattedRect<FillLinear>(surface, region, fill);
else return _rasterGradientMaskedRect<FillLinear>(surface, region, fill);
@@ -1902,10 +1925,16 @@ void rasterPremultiply(Surface* surface)
}
-bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id)
+bool rasterGradientShape(SwSurface* surface, SwShape* shape, const Fill* fdata, uint8_t opacity)
{
if (!shape->fill) return false;
+ if (auto color = fillFetchSolid(shape->fill, fdata)) {
+ auto a = MULTIPLY(color->a, opacity);
+ return a > 0 ? rasterShape(surface, shape, color->r, color->g, color->b, a) : true;
+ }
+
+ auto id = fdata->identifier();
if (shape->fastTrack) {
if (id == TVG_CLASS_ID_LINEAR) return _rasterLinearGradientRect(surface, shape->bbox, shape->fill);
else if (id == TVG_CLASS_ID_RADIAL)return _rasterRadialGradientRect(surface, shape->bbox, shape->fill);
@@ -1917,10 +1946,16 @@ bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id)
}
-bool rasterGradientStroke(SwSurface* surface, SwShape* shape, unsigned id)
+bool rasterGradientStroke(SwSurface* surface, SwShape* shape, const Fill* fdata, uint8_t opacity)
{
if (!shape->stroke || !shape->stroke->fill || !shape->strokeRle) return false;
+ if (auto color = fillFetchSolid(shape->stroke->fill, fdata)) {
+ auto a = MULTIPLY(color->a, opacity);
+ return a > 0 ? rasterStroke(surface, shape, color->r, color->g, color->b, a) : true;
+ }
+
+ auto id = fdata->identifier();
if (id == TVG_CLASS_ID_LINEAR) return _rasterLinearGradientRle(surface, shape->strokeRle, shape->stroke->fill);
else if (id == TVG_CLASS_ID_RADIAL) return _rasterRadialGradientRle(surface, shape->strokeRle, shape->stroke->fill);
@@ -1952,13 +1987,12 @@ bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint
}
-bool rasterImage(SwSurface* surface, SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox& bbox, uint8_t opacity)
+bool rasterImage(SwSurface* surface, SwImage* image, const Matrix& transform, const SwBBox& bbox, uint8_t opacity)
{
//Outside of the viewport, skip the rendering
if (bbox.max.x < 0 || bbox.max.y < 0 || bbox.min.x >= static_cast<SwCoord>(surface->w) || bbox.min.y >= static_cast<SwCoord>(surface->h)) return true;
- if (mesh && mesh->triangleCnt > 0) return _rasterTexmapPolygonMesh(surface, image, mesh, transform, &bbox, opacity);
- else return _rasterImage(surface, image, transform, bbox, opacity);
+ return _rasterImage(surface, image, transform, bbox, opacity);
}
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h
index cfce7785c7..88ef2118f2 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRasterTexmap.h
@@ -53,12 +53,10 @@ static bool _arrange(const SwImage* image, const SwBBox* region, int& yStart, in
regionBottom = image->rle->spans[image->rle->size - 1].y;
}
- if (yStart >= regionBottom) return false;
-
if (yStart < regionTop) yStart = regionTop;
if (yEnd > regionBottom) yEnd = regionBottom;
- return true;
+ return yEnd > yStart;
}
@@ -868,10 +866,8 @@ static void _calcVertCoverage(AALine *lines, int32_t eidx, int32_t y, int32_t re
static void _calcHorizCoverage(AALine *lines, int32_t eidx, int32_t y, int32_t x, int32_t x2)
{
- if (lines[y].length[eidx] < abs(x - x2)) {
- lines[y].length[eidx] = abs(x - x2);
- lines[y].coverage[eidx] = (255 / (lines[y].length[eidx] + 1));
- }
+ lines[y].length[eidx] = abs(x - x2);
+ lines[y].coverage[eidx] = (255 / (lines[y].length[eidx] + 1));
}
@@ -897,9 +893,14 @@ static void _calcAAEdge(AASpans *aaSpans, int32_t eidx)
ptx[1] = tx[1]; \
} while (0)
+ struct Point
+ {
+ int32_t x, y;
+ };
+
int32_t y = 0;
- SwPoint pEdge = {-1, -1}; //previous edge point
- SwPoint edgeDiff = {0, 0}; //temporary used for point distance
+ Point pEdge = {-1, -1}; //previous edge point
+ Point edgeDiff = {0, 0}; //temporary used for point distance
/* store bigger to tx[0] between prev and current edge's x positions. */
int32_t tx[2] = {0, 0};
@@ -1024,6 +1025,7 @@ static void _calcAAEdge(AASpans *aaSpans, int32_t eidx)
static bool _apply(SwSurface* surface, AASpans* aaSpans)
{
+ auto end = surface->buf32 + surface->h * surface->stride;
auto y = aaSpans->yStart;
uint32_t pixel;
uint32_t* dst;
@@ -1044,8 +1046,13 @@ static bool _apply(SwSurface* surface, AASpans* aaSpans)
dst = surface->buf32 + (offset + line->x[0]);
if (line->x[0] > 1) pixel = *(dst - 1);
else pixel = *dst;
-
pos = 1;
+
+ //exceptional handling. out of memory bound.
+ if (dst + line->length[0] >= end) {
+ pos += (dst + line->length[0] - end);
+ }
+
while (pos <= line->length[0]) {
*dst = INTERPOLATE(*dst, pixel, line->coverage[0] * pos);
++dst;
@@ -1053,17 +1060,21 @@ static bool _apply(SwSurface* surface, AASpans* aaSpans)
}
//Right edge
- dst = surface->buf32 + (offset + line->x[1] - 1);
+ dst = surface->buf32 + offset + line->x[1] - 1;
+
if (line->x[1] < (int32_t)(surface->w - 1)) pixel = *(dst + 1);
else pixel = *dst;
+ pos = line->length[1];
- pos = width;
- while ((int32_t)(width - line->length[1]) < pos) {
- *dst = INTERPOLATE(*dst, pixel, 255 - (line->coverage[1] * (line->length[1] - (width - pos))));
+ //exceptional handling. out of memory bound.
+ if (dst - pos < surface->buf32) --pos;
+
+ while (pos > 0) {
+ *dst = INTERPOLATE(*dst, pixel, 255 - (line->coverage[1] * pos));
--dst;
--pos;
}
- }
+ }
y++;
}
@@ -1084,7 +1095,7 @@ static bool _apply(SwSurface* surface, AASpans* aaSpans)
| / |
3 -- 2
*/
-static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox* region, uint8_t opacity)
+static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const Matrix& transform, const SwBBox* region, uint8_t opacity)
{
if (surface->channelSize == sizeof(uint8_t)) {
TVGERR("SW_ENGINE", "Not supported grayscale Textmap polygon!");
@@ -1092,7 +1103,7 @@ static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const
}
//Exceptions: No dedicated drawing area?
- if ((!image->rle && !region) || (image->rle && image->rle->size == 0)) return false;
+ if ((!image->rle && !region) || (image->rle && image->rle->size == 0)) return true;
/* Prepare vertices.
shift XY coordinates to match the sub-pixeling technique. */
@@ -1104,7 +1115,7 @@ static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const
float ys = FLT_MAX, ye = -1.0f;
for (int i = 0; i < 4; i++) {
- if (transform) vertices[i].pt *= *transform;
+ vertices[i].pt *= transform;
if (vertices[i].pt.y < ys) ys = vertices[i].pt.y;
if (vertices[i].pt.y > ye) ye = vertices[i].pt.y;
}
@@ -1135,68 +1146,3 @@ static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const
#endif
return _apply(surface, aaSpans);
}
-
-
-/*
- Provide any number of triangles to draw a mesh using the supplied image.
- Indexes are not used, so each triangle (Polygon) vertex has to be defined, even if they copy the previous one.
- Example:
-
- 0 -- 1 0 -- 1 0
- | / | --> | / / |
- | / | | / / |
- 2 -- 3 2 1 -- 2
-
- Should provide two Polygons, one for each triangle.
- // TODO: region?
-*/
-static bool _rasterTexmapPolygonMesh(SwSurface* surface, const SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox* region, uint8_t opacity)
-{
- if (surface->channelSize == sizeof(uint8_t)) {
- TVGERR("SW_ENGINE", "Not supported grayscale Textmap polygon mesh!");
- return false;
- }
-
- //Exceptions: No dedicated drawing area?
- if ((!image->rle && !region) || (image->rle && image->rle->size == 0)) return false;
-
- // Step polygons once to transform
- auto transformedTris = (Polygon*)malloc(sizeof(Polygon) * mesh->triangleCnt);
- float ys = FLT_MAX, ye = -1.0f;
- for (uint32_t i = 0; i < mesh->triangleCnt; i++) {
- transformedTris[i] = mesh->triangles[i];
- transformedTris[i].vertex[0].pt *= *transform;
- transformedTris[i].vertex[1].pt *= *transform;
- transformedTris[i].vertex[2].pt *= *transform;
-
- if (transformedTris[i].vertex[0].pt.y < ys) ys = transformedTris[i].vertex[0].pt.y;
- else if (transformedTris[i].vertex[0].pt.y > ye) ye = transformedTris[i].vertex[0].pt.y;
- if (transformedTris[i].vertex[1].pt.y < ys) ys = transformedTris[i].vertex[1].pt.y;
- else if (transformedTris[i].vertex[1].pt.y > ye) ye = transformedTris[i].vertex[1].pt.y;
- if (transformedTris[i].vertex[2].pt.y < ys) ys = transformedTris[i].vertex[2].pt.y;
- else if (transformedTris[i].vertex[2].pt.y > ye) ye = transformedTris[i].vertex[2].pt.y;
-
- // Convert normalized UV coordinates to image coordinates
- transformedTris[i].vertex[0].uv.x *= (float)image->w;
- transformedTris[i].vertex[0].uv.y *= (float)image->h;
- transformedTris[i].vertex[1].uv.x *= (float)image->w;
- transformedTris[i].vertex[1].uv.y *= (float)image->h;
- transformedTris[i].vertex[2].uv.x *= (float)image->w;
- transformedTris[i].vertex[2].uv.y *= (float)image->h;
- }
-
- // Get AA spans and step polygons again to draw
- if (auto aaSpans = _AASpans(ys, ye, image, region)) {
- for (uint32_t i = 0; i < mesh->triangleCnt; i++) {
- _rasterPolygonImage(surface, image, region, transformedTris[i], aaSpans, opacity);
- }
-#if 0
- if (_compositing(surface) && _masking(surface) && !_direct(surface->compositor->method)) {
- _compositeMaskImage(surface, &surface->compositor->image, surface->compositor->bbox);
- }
-#endif
- _apply(surface, aaSpans);
- }
- free(transformedTris);
- return true;
-}
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
index 350f333405..6470089c7c 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
@@ -39,7 +39,7 @@ struct SwTask : Task
SwSurface* surface = nullptr;
SwMpool* mpool = nullptr;
SwBBox bbox = {{0, 0}, {0, 0}}; //Whole Rendering Region
- Matrix* transform = nullptr;
+ Matrix transform;
Array<RenderData> clips;
RenderUpdateFlag flags = RenderUpdateFlag::None;
uint8_t opacity;
@@ -68,10 +68,7 @@ struct SwTask : Task
virtual bool clip(SwRleData* target) = 0;
virtual SwRleData* rle() = 0;
- virtual ~SwTask()
- {
- free(transform);
- }
+ virtual ~SwTask() {}
};
@@ -100,11 +97,9 @@ struct SwShapeTask : SwTask
if (!rshape->stroke->fill && (MULTIPLY(rshape->stroke->color[3], opacity) == 0)) return 0.0f;
if (mathZero(rshape->stroke->trim.begin - rshape->stroke->trim.end)) return 0.0f;
- if (transform) return (width * sqrt(transform->e11 * transform->e11 + transform->e12 * transform->e12));
- else return width;
+ return (width * sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12));
}
-
bool clip(SwRleData* target) override
{
if (shape.fastTrack) rleClipRect(target, &bbox);
@@ -203,64 +198,10 @@ struct SwShapeTask : SwTask
};
-struct SwSceneTask : SwTask
-{
- Array<RenderData> scene; //list of paints render data (SwTask)
- SwRleData* sceneRle = nullptr;
-
- bool clip(SwRleData* target) override
- {
- //Only one shape
- if (scene.count == 1) {
- return static_cast<SwTask*>(*scene.data)->clip(target);
- }
-
- //More than one shapes
- if (sceneRle) rleClipPath(target, sceneRle);
- else TVGLOG("SW_ENGINE", "No clippers in a scene?");
-
- return true;
- }
-
- SwRleData* rle() override
- {
- return sceneRle;
- }
-
- void run(unsigned tid) override
- {
- //TODO: Skip the run if the scene hans't changed.
- if (!sceneRle) sceneRle = static_cast<SwRleData*>(calloc(1, sizeof(SwRleData)));
- else rleReset(sceneRle);
-
- //Merge shapes if it has more than one shapes
- if (scene.count > 1) {
- //Merge first two clippers
- auto clipper1 = static_cast<SwTask*>(*scene.data);
- auto clipper2 = static_cast<SwTask*>(*(scene.data + 1));
-
- rleMerge(sceneRle, clipper1->rle(), clipper2->rle());
-
- //Unify the remained clippers
- for (auto rd = scene.begin() + 2; rd < scene.end(); ++rd) {
- auto clipper = static_cast<SwTask*>(*rd);
- rleMerge(sceneRle, sceneRle, clipper->rle());
- }
- }
- }
-
- void dispose() override
- {
- rleFree(sceneRle);
- }
-};
-
-
struct SwImageTask : SwTask
{
SwImage image;
Surface* source; //Image source
- const RenderMesh* mesh = nullptr; //Should be valid ptr in action
bool clip(SwRleData* target) override
{
@@ -293,10 +234,9 @@ struct SwImageTask : SwTask
imageReset(&image);
if (!image.data || image.w == 0 || image.h == 0) goto end;
- if (!imagePrepare(&image, mesh, transform, clipRegion, bbox, mpool, tid)) goto end;
+ if (!imagePrepare(&image, transform, clipRegion, bbox, mpool, tid)) goto end;
- // TODO: How do we clip the triangle mesh? Only clip non-meshed images for now
- if (mesh->triangleCnt == 0 && clips.count > 0) {
+ if (clips.count > 0) {
if (!imageGenRle(&image, bbox, false)) goto end;
if (image.rle) {
//Clear current task memorypool here if the clippers would use the same memory pool
@@ -336,7 +276,7 @@ static void _renderFill(SwShapeTask* task, SwSurface* surface, uint8_t opacity)
{
uint8_t r, g, b, a;
if (auto fill = task->rshape->fill) {
- rasterGradientShape(surface, &task->shape, fill->identifier());
+ rasterGradientShape(surface, &task->shape, fill, opacity);
} else {
task->rshape->fillColor(&r, &g, &b, &a);
a = MULTIPLY(opacity, a);
@@ -348,7 +288,7 @@ static void _renderStroke(SwShapeTask* task, SwSurface* surface, uint8_t opacity
{
uint8_t r, g, b, a;
if (auto strokeFill = task->rshape->strokeFill()) {
- rasterGradientStroke(surface, &task->shape, strokeFill->identifier());
+ rasterGradientStroke(surface, &task->shape, strokeFill, opacity);
} else {
if (task->rshape->strokeColor(&r, &g, &b, &a)) {
a = MULTIPLY(opacity, a);
@@ -480,7 +420,7 @@ bool SwRenderer::renderImage(RenderData data)
if (task->opacity == 0) return true;
- return rasterImage(surface, &task->image, task->mesh, task->transform, task->bbox, task->opacity);
+ return rasterImage(surface, &task->image, task->transform, task->bbox, task->opacity);
}
@@ -506,7 +446,7 @@ bool SwRenderer::renderShape(RenderData data)
}
-bool SwRenderer::blend(BlendMethod method)
+bool SwRenderer::blend(BlendMethod method, bool direct)
{
if (surface->blendMethod == method) return true;
surface->blendMethod = method;
@@ -519,7 +459,7 @@ bool SwRenderer::blend(BlendMethod method)
surface->blender = opBlendScreen;
break;
case BlendMethod::Multiply:
- surface->blender = opBlendMultiply;
+ surface->blender = direct ? opBlendDirectMultiply : opBlendMultiply;
break;
case BlendMethod::Overlay:
surface->blender = opBlendOverlay;
@@ -688,7 +628,8 @@ bool SwRenderer::endComposite(Compositor* cmp)
//Default is alpha blending
if (p->method == CompositeMethod::None) {
- return rasterImage(surface, &p->image, nullptr, nullptr, p->bbox, p->opacity);
+ Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1};
+ return rasterImage(surface, &p->image, m, p->bbox, p->opacity);
}
return true;
@@ -714,7 +655,7 @@ void SwRenderer::dispose(RenderData data)
}
-void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
+void* SwRenderer::prepareCommon(SwTask* task, const Matrix& transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
{
if (!surface) return task;
if (flags == RenderUpdateFlag::None) return task;
@@ -727,20 +668,11 @@ void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform,
}
task->clips = clips;
-
- if (transform) {
- if (!task->transform) task->transform = static_cast<Matrix*>(malloc(sizeof(Matrix)));
- *task->transform = transform->m;
- } else {
- if (task->transform) free(task->transform);
- task->transform = nullptr;
- }
-
+ task->transform = transform;
+
//zero size?
- if (task->transform) {
- if (task->transform->e11 == 0.0f && task->transform->e12 == 0.0f) return task; //zero width
- if (task->transform->e21 == 0.0f && task->transform->e22 == 0.0f) return task; //zero height
- }
+ if (task->transform.e11 == 0.0f && task->transform.e12 == 0.0f) return task; //zero width
+ if (task->transform.e21 == 0.0f && task->transform.e22 == 0.0f) return task; //zero height
task->opacity = opacity;
task->surface = surface;
@@ -762,7 +694,7 @@ void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform,
}
-RenderData SwRenderer::prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
+RenderData SwRenderer::prepare(Surface* surface, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
{
//prepare task
auto task = static_cast<SwImageTask*>(data);
@@ -770,33 +702,12 @@ RenderData SwRenderer::prepare(Surface* surface, const RenderMesh* mesh, RenderD
else task->done();
task->source = surface;
- task->mesh = mesh;
-
- return prepareCommon(task, transform, clips, opacity, flags);
-}
-
-
-RenderData SwRenderer::prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
-{
- //prepare task
- auto task = static_cast<SwSceneTask*>(data);
- if (!task) task = new SwSceneTask;
- else task->done();
-
- task->scene = scene;
-
- //TODO: Failed threading them. It would be better if it's possible.
- //See: https://github.com/thorvg/thorvg/issues/1409
- //Guarantee composition targets get ready.
- for (auto task = scene.begin(); task < scene.end(); ++task) {
- static_cast<SwTask*>(*task)->done();
- }
return prepareCommon(task, transform, clips, opacity, flags);
}
-RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper)
+RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper)
{
//prepare task
auto task = static_cast<SwShapeTask*>(data);
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h
index 57be558988..7aa6d89aaa 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h
@@ -36,9 +36,8 @@ namespace tvg
class SwRenderer : public RenderMethod
{
public:
- RenderData prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) override;
- RenderData prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
- RenderData prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
+ RenderData prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) override;
+ RenderData prepare(Surface* surface, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
bool preRender() override;
bool renderShape(RenderData data) override;
bool renderImage(RenderData data) override;
@@ -47,7 +46,7 @@ public:
RenderRegion region(RenderData data) override;
RenderRegion viewport() override;
bool viewport(const RenderRegion& vp) override;
- bool blend(BlendMethod method) override;
+ bool blend(BlendMethod method, bool direct) override;
ColorSpace colorSpace() override;
const Surface* mainSurface() override;
@@ -77,7 +76,7 @@ private:
SwRenderer();
~SwRenderer();
- RenderData prepareCommon(SwTask* task, const RenderTransform* transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags);
+ RenderData prepareCommon(SwTask* task, const Matrix& transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags);
};
}
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp
index 25c6cd90b9..42b08de6a5 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRle.cpp
@@ -358,7 +358,7 @@ static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoor
rle->spans = static_cast<SwSpan*>(realloc(rle->spans, rle->alloc * sizeof(SwSpan)));
}
}
-
+
//Clip x range
SwCoord xOver = 0;
if (x + acount >= rw.cellMax.x) xOver -= (x + acount - rw.cellMax.x);
@@ -822,46 +822,6 @@ static SwSpan* _intersectSpansRect(const SwBBox *bbox, const SwRleData *targetRl
}
-static SwSpan* _mergeSpansRegion(const SwRleData *clip1, const SwRleData *clip2, SwSpan *outSpans)
-{
- auto out = outSpans;
- auto spans1 = clip1->spans;
- auto end1 = clip1->spans + clip1->size;
- auto spans2 = clip2->spans;
- auto end2 = clip2->spans + clip2->size;
-
- //list two spans up in y order
- //TODO: Remove duplicated regions?
- while (spans1 < end1 && spans2 < end2) {
- while (spans1 < end1 && spans1->y <= spans2->y) {
- *out = *spans1;
- ++spans1;
- ++out;
- }
- if (spans1 >= end1) break;
- while (spans2 < end2 && spans2->y <= spans1->y) {
- *out = *spans2;
- ++spans2;
- ++out;
- }
- }
-
- //Leftovers
- while (spans1 < end1) {
- *out = *spans1;
- ++spans1;
- ++out;
- }
- while (spans2 < end2) {
- *out = *spans2;
- ++spans2;
- ++out;
- }
-
- return out;
-}
-
-
void _replaceClipSpan(SwRleData *rle, SwSpan* clippedSpans, uint32_t size)
{
free(rle->spans);
@@ -1030,45 +990,6 @@ void rleFree(SwRleData* rle)
}
-void rleMerge(SwRleData* rle, SwRleData* clip1, SwRleData* clip2)
-{
- if (!rle || (!clip1 && !clip2)) return;
- if (clip1 && clip1->size == 0 && clip2 && clip2->size == 0) return;
-
- TVGLOG("SW_ENGINE", "Unifying Rle!");
-
- //clip1 is empty, just copy clip2
- if (!clip1 || clip1->size == 0) {
- if (clip2) {
- auto spans = static_cast<SwSpan*>(malloc(sizeof(SwSpan) * (clip2->size)));
- memcpy(spans, clip2->spans, clip2->size);
- _replaceClipSpan(rle, spans, clip2->size);
- } else {
- _replaceClipSpan(rle, nullptr, 0);
- }
- return;
- }
-
- //clip2 is empty, just copy clip1
- if (!clip2 || clip2->size == 0) {
- if (clip1) {
- auto spans = static_cast<SwSpan*>(malloc(sizeof(SwSpan) * (clip1->size)));
- memcpy(spans, clip1->spans, clip1->size);
- _replaceClipSpan(rle, spans, clip1->size);
- } else {
- _replaceClipSpan(rle, nullptr, 0);
- }
- return;
- }
-
- auto spanCnt = clip1->size + clip2->size;
- auto spans = static_cast<SwSpan*>(malloc(sizeof(SwSpan) * spanCnt));
- auto spansEnd = _mergeSpansRegion(clip1, clip2, spans);
-
- _replaceClipSpan(rle, spans, spansEnd - spans);
-}
-
-
void rleClipPath(SwRleData *rle, const SwRleData *clip)
{
if (rle->size == 0 || clip->size == 0) return;
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp
index 4f069ece97..96c3bc28b9 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp
@@ -49,7 +49,7 @@ static bool _outlineEnd(SwOutline& outline)
}
-static bool _outlineMoveTo(SwOutline& outline, const Point* to, const Matrix* transform, bool closed = false)
+static bool _outlineMoveTo(SwOutline& outline, const Point* to, const Matrix& transform, bool closed = false)
{
//make it a contour, if the last contour is not closed yet.
if (!closed) _outlineEnd(outline);
@@ -60,14 +60,14 @@ static bool _outlineMoveTo(SwOutline& outline, const Point* to, const Matrix* tr
}
-static void _outlineLineTo(SwOutline& outline, const Point* to, const Matrix* transform)
+static void _outlineLineTo(SwOutline& outline, const Point* to, const Matrix& transform)
{
outline.pts.push(mathTransform(to, transform));
outline.types.push(SW_CURVE_TYPE_POINT);
}
-static void _outlineCubicTo(SwOutline& outline, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix* transform)
+static void _outlineCubicTo(SwOutline& outline, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix& transform)
{
outline.pts.push(mathTransform(ctrl1, transform));
outline.types.push(SW_CURVE_TYPE_CUBIC);
@@ -99,7 +99,7 @@ static bool _outlineClose(SwOutline& outline)
}
-static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* transform)
+static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix& transform)
{
Line cur = {dash.ptCur, *to};
auto len = lineLength(cur.pt1, cur.pt2);
@@ -160,7 +160,7 @@ static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* trans
}
-static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix* transform)
+static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix& transform)
{
Bezier cur = {dash.ptCur, *ctrl1, *ctrl2, *to};
auto len = bezLength(cur);
@@ -221,7 +221,7 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct
}
-static void _dashClose(SwDashStroke& dash, const Matrix* transform)
+static void _dashClose(SwDashStroke& dash, const Matrix& transform)
{
_dashLineTo(dash, &dash.ptStart, transform);
}
@@ -245,10 +245,10 @@ static void _dashMoveTo(SwDashStroke& dash, uint32_t offIdx, float offset, const
}
-static void _trimPattern(SwDashStroke* dash, const RenderShape* rshape, float length)
+static void _trimPattern(SwDashStroke* dash, const RenderShape* rshape, float length, float trimBegin, float trimEnd)
{
- auto begin = length * rshape->stroke->trim.begin;
- auto end = length * rshape->stroke->trim.end;
+ auto begin = length * trimBegin;
+ auto end = length * trimEnd;
//default
if (end > begin) {
@@ -324,7 +324,7 @@ static float _outlineLength(const RenderShape* rshape, uint32_t shiftPts, uint32
}
-static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* transform, bool trimmed, SwMpool* mpool, unsigned tid)
+static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix& transform, bool trimmed, SwMpool* mpool, unsigned tid)
{
const PathCommand* cmds = rshape->path.cmds.data;
auto cmdCnt = rshape->path.cmds.count;
@@ -341,6 +341,8 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
auto offset = 0.0f;
dash.cnt = rshape->strokeDash((const float**)&dash.pattern, &offset);
auto simultaneous = rshape->stroke->trim.simultaneous;
+ float trimBegin = 0.0f, trimEnd = 1.0f;
+ if (trimmed) rshape->stroke->strokeTrim(trimBegin, trimEnd);
if (dash.cnt == 0) {
if (trimmed) dash.pattern = (float*)malloc(sizeof(float) * 4);
@@ -372,7 +374,7 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
//must begin with moveTo
if (cmds[0] == PathCommand::MoveTo) {
- if (trimmed) _trimPattern(&dash, rshape, _outlineLength(rshape, 0, 0, simultaneous));
+ if (trimmed) _trimPattern(&dash, rshape, _outlineLength(rshape, 0, 0, simultaneous), trimBegin, trimEnd);
_dashMoveTo(dash, offIdx, offset, pts);
cmds++;
pts++;
@@ -387,7 +389,7 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
case PathCommand::MoveTo: {
if (trimmed) {
if (simultaneous) {
- _trimPattern(&dash, rshape, _outlineLength(rshape, pts - startPts, cmds - startCmds, true));
+ _trimPattern(&dash, rshape, _outlineLength(rshape, pts - startPts, cmds - startCmds, true), trimBegin, trimEnd);
_dashMoveTo(dash, offIdx, offset, pts);
} else _dashMoveTo(dash, pts);
} else _dashMoveTo(dash, offIdx, offset, pts);
@@ -436,7 +438,7 @@ static bool _axisAlignedRect(const SwOutline* outline)
}
-static bool _genOutline(SwShape* shape, const RenderShape* rshape, const Matrix* transform, SwMpool* mpool, unsigned tid, bool hasComposite)
+static bool _genOutline(SwShape* shape, const RenderShape* rshape, const Matrix& transform, SwMpool* mpool, unsigned tid, bool hasComposite)
{
const PathCommand* cmds = rshape->path.cmds.data;
auto cmdCnt = rshape->path.cmds.count;
@@ -492,7 +494,7 @@ static bool _genOutline(SwShape* shape, const RenderShape* rshape, const Matrix*
/* External Class Implementation */
/************************************************************************/
-bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite)
+bool shapePrepare(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid, bool hasComposite)
{
if (!_genOutline(shape, rshape, transform, mpool, tid, hasComposite)) return false;
if (!mathUpdateOutlineBBox(shape->outline, clipRegion, renderRegion, shape->fastTrack)) return false;
@@ -575,7 +577,7 @@ void shapeDelStroke(SwShape* shape)
}
-void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix* transform)
+void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix& transform)
{
if (!shape->stroke) shape->stroke = static_cast<SwStroke*>(calloc(1, sizeof(SwStroke)));
auto stroke = shape->stroke;
@@ -586,7 +588,7 @@ void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix* t
}
-bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid)
+bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix& transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid)
{
SwOutline* shapeOutline = nullptr;
SwOutline* strokeOutline = nullptr;
@@ -629,13 +631,13 @@ clear:
}
-bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable)
+bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix& transform, SwSurface* surface, uint8_t opacity, bool ctable)
{
return fillGenColorTable(shape->fill, fill, transform, surface, opacity, ctable);
}
-bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable)
+bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix& transform, SwSurface* surface, uint8_t opacity, bool ctable)
{
return fillGenColorTable(shape->stroke->fill, fill, transform, surface, opacity, ctable);
}
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp
index 18f5f3eca8..e0e74ce53c 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp
+++ b/thirdparty/thorvg/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) {
@@ -805,15 +811,10 @@ void strokeFree(SwStroke* stroke)
}
-void strokeReset(SwStroke* stroke, const RenderShape* rshape, const Matrix* transform)
+void strokeReset(SwStroke* stroke, const RenderShape* rshape, const Matrix& transform)
{
- if (transform) {
- stroke->sx = sqrtf(powf(transform->e11, 2.0f) + powf(transform->e21, 2.0f));
- stroke->sy = sqrtf(powf(transform->e12, 2.0f) + powf(transform->e22, 2.0f));
- } else {
- stroke->sx = stroke->sy = 1.0f;
- }
-
+ stroke->sx = sqrtf(powf(transform.e11, 2.0f) + powf(transform.e21, 2.0f));
+ stroke->sy = sqrtf(powf(transform.e12, 2.0f) + powf(transform.e22, 2.0f));
stroke->width = HALF_STROKE(rshape->strokeWidth());
stroke->cap = rshape->strokeCap();
stroke->miterlimit = static_cast<SwFixed>(rshape->strokeMiterlimit() * 65536.0f);
diff --git a/thirdparty/thorvg/src/renderer/tvgAccessor.cpp b/thirdparty/thorvg/src/renderer/tvgAccessor.cpp
index 903437f29d..a144726804 100644
--- a/thirdparty/thorvg/src/renderer/tvgAccessor.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgAccessor.cpp
@@ -21,20 +21,21 @@
*/
#include "tvgIteratorAccessor.h"
+#include "tvgCompressor.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
-static bool accessChildren(Iterator* it, function<bool(const Paint* paint)> func)
+static bool accessChildren(Iterator* it, function<bool(const Paint* paint, void* data)> func, void* data)
{
while (auto child = it->next()) {
//Access the child
- if (!func(child)) return false;
+ if (!func(child, data)) return false;
//Access the children of the child
if (auto it2 = IteratorAccessor::iterator(child)) {
- if (!accessChildren(it2, func)) {
+ if (!accessChildren(it2, func, data)) {
delete(it2);
return false;
}
@@ -44,26 +45,46 @@ static bool accessChildren(Iterator* it, function<bool(const Paint* paint)> func
return true;
}
+
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
-unique_ptr<Picture> Accessor::set(unique_ptr<Picture> picture, function<bool(const Paint* paint)> func) noexcept
+TVG_DEPRECATED unique_ptr<Picture> Accessor::set(unique_ptr<Picture> picture, function<bool(const Paint* paint)> func) noexcept
+{
+ auto backward = [](const tvg::Paint* paint, void* data) -> bool
+ {
+ auto func = reinterpret_cast<function<bool(const Paint* paint)>*>(data);
+ if (!(*func)(paint)) return false;
+ return true;
+ };
+
+ set(picture.get(), backward, reinterpret_cast<void*>(&func));
+ return picture;
+}
+
+
+Result Accessor::set(const Picture* picture, function<bool(const Paint* paint, void* data)> func, void* data) noexcept
{
- auto p = picture.get();
- if (!p || !func) return picture;
+ if (!picture || !func) return Result::InvalidArguments;
//Use the Preorder Tree-Search
//Root
- if (!func(p)) return picture;
+ if (!func(picture, data)) return Result::Success;
//Children
- if (auto it = IteratorAccessor::iterator(p)) {
- accessChildren(it, func);
+ if (auto it = IteratorAccessor::iterator(picture)) {
+ accessChildren(it, func, data);
delete(it);
}
- return picture;
+ return Result::Success;
+}
+
+
+uint32_t Accessor::id(const char* name) noexcept
+{
+ return djb2Encode(name);
}
diff --git a/thirdparty/thorvg/src/renderer/tvgAnimation.cpp b/thirdparty/thorvg/src/renderer/tvgAnimation.cpp
index be6c2dc7de..6c4711e8c1 100644
--- a/thirdparty/thorvg/src/renderer/tvgAnimation.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgAnimation.cpp
@@ -95,7 +95,7 @@ float Animation::duration() const noexcept
Result Animation::segment(float begin, float end) noexcept
{
- if (begin < 0.0 || end > 1.0 || begin >= end) return Result::InvalidArguments;
+ if (begin < 0.0 || end > 1.0 || begin > end) return Result::InvalidArguments;
auto loader = pImpl->picture->pImpl->loader;
if (!loader) return Result::InsufficientCondition;
diff --git a/thirdparty/thorvg/src/renderer/tvgCanvas.h b/thirdparty/thorvg/src/renderer/tvgCanvas.h
index 81fd1b7d6f..199e823034 100644
--- a/thirdparty/thorvg/src/renderer/tvgCanvas.h
+++ b/thirdparty/thorvg/src/renderer/tvgCanvas.h
@@ -93,11 +93,13 @@ struct Canvas::Impl
auto flag = RenderUpdateFlag::None;
if (status == Status::Damanged || force) flag = RenderUpdateFlag::All;
+ auto m = Matrix{1, 0, 0, 0, 1, 0, 0, 0, 1};
+
if (paint) {
- paint->pImpl->update(renderer, nullptr, clips, 255, flag);
+ paint->pImpl->update(renderer, m, clips, 255, flag);
} else {
for (auto paint : paints) {
- paint->pImpl->update(renderer, nullptr, clips, 255, flag);
+ paint->pImpl->update(renderer, m, clips, 255, flag);
}
}
status = Status::Updating;
diff --git a/thirdparty/thorvg/src/renderer/tvgInitializer.cpp b/thirdparty/thorvg/src/renderer/tvgInitializer.cpp
index 76d89b40ed..c57b20779c 100644
--- a/thirdparty/thorvg/src/renderer/tvgInitializer.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgInitializer.cpp
@@ -54,36 +54,30 @@ static constexpr bool operator &(CanvasEngine a, CanvasEngine b)
return int(a) & int(b);
}
-static bool _buildVersionInfo()
+static bool _buildVersionInfo(uint32_t* major, uint32_t* minor, uint32_t* micro)
{
- auto SRC = THORVG_VERSION_STRING; //ex) 0.3.99
- auto p = SRC;
+ auto VER = THORVG_VERSION_STRING;
+ auto p = VER;
const char* x;
- char major[3];
- x = strchr(p, '.');
- if (!x) return false;
- memcpy(major, p, x - p);
- major[x - p] = '\0';
+ if (!(x = strchr(p, '.'))) return false;
+ uint32_t majorVal = atoi(p);
p = x + 1;
- char minor[3];
- x = strchr(p, '.');
- if (!x) return false;
- memcpy(minor, p, x - p);
- minor[x - p] = '\0';
+ if (!(x = strchr(p, '.'))) return false;
+ uint32_t minorVal = atoi(p);
p = x + 1;
- char micro[3];
- x = SRC + strlen(THORVG_VERSION_STRING);
- memcpy(micro, p, x - p);
- micro[x - p] = '\0';
+ uint32_t microVal = atoi(p);
char sum[7];
- snprintf(sum, sizeof(sum), "%s%s%s", major, minor, micro);
-
+ snprintf(sum, sizeof(sum), "%d%02d%02d", majorVal, minorVal, microVal);
_version = atoi(sum);
+ if (major) *major = majorVal;
+ if (minor) *minor = minorVal;
+ if (micro) *micro = microVal;
+
return true;
}
@@ -122,7 +116,7 @@ Result Initializer::init(CanvasEngine engine, uint32_t threads) noexcept
if (_initCnt++ > 0) return Result::Success;
- if (!_buildVersionInfo()) return Result::Unknown;
+ if (!_buildVersionInfo(nullptr, nullptr, nullptr)) return Result::Unknown;
if (!LoaderMgr::init()) return Result::Unknown;
@@ -172,8 +166,14 @@ Result Initializer::term(CanvasEngine engine) noexcept
}
+const char* Initializer::version(uint32_t* major, uint32_t* minor, uint32_t* micro) noexcept
+{
+ if ((!major && ! minor && !micro) || _buildVersionInfo(major, minor, micro)) return THORVG_VERSION_STRING;
+ return nullptr;
+}
+
+
uint16_t THORVG_VERSION_NUMBER()
{
return _version;
}
-
diff --git a/thirdparty/thorvg/src/renderer/tvgLoadModule.h b/thirdparty/thorvg/src/renderer/tvgLoadModule.h
index c750683771..1b81d81a4f 100644
--- a/thirdparty/thorvg/src/renderer/tvgLoadModule.h
+++ b/thirdparty/thorvg/src/renderer/tvgLoadModule.h
@@ -101,7 +101,8 @@ struct FontLoader : LoadModule
FontLoader(FileType type) : LoadModule(type) {}
- virtual bool request(Shape* shape, char* text, bool italic = false) = 0;
+ virtual bool request(Shape* shape, char* text) = 0;
+ virtual bool transform(Paint* paint, float fontSize, bool italic) = 0;
};
#endif //_TVG_LOAD_MODULE_H_
diff --git a/thirdparty/thorvg/src/renderer/tvgPaint.cpp b/thirdparty/thorvg/src/renderer/tvgPaint.cpp
index 0ce6540f20..37813b19ef 100644
--- a/thirdparty/thorvg/src/renderer/tvgPaint.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgPaint.cpp
@@ -41,7 +41,7 @@
}
-static Result _clipRect(RenderMethod* renderer, const Point* pts, const RenderTransform* pTransform, RenderTransform* rTransform, RenderRegion& before)
+static Result _clipRect(RenderMethod* renderer, const Point* pts, const Matrix& pm, const Matrix& rm, RenderRegion& before)
{
//sorting
Point tmp[4];
@@ -50,8 +50,8 @@ static Result _clipRect(RenderMethod* renderer, const Point* pts, const RenderTr
for (int i = 0; i < 4; ++i) {
tmp[i] = pts[i];
- if (rTransform) tmp[i] *= rTransform->m;
- if (pTransform) tmp[i] *= pTransform->m;
+ tmp[i] *= rm;
+ tmp[i] *= pm;
if (tmp[i].x < min.x) min.x = tmp[i].x;
if (tmp[i].x > max.x) max.x = tmp[i].x;
if (tmp[i].y < min.y) min.y = tmp[i].y;
@@ -73,7 +73,7 @@ static Result _clipRect(RenderMethod* renderer, const Point* pts, const RenderTr
}
-static Result _compFastTrack(RenderMethod* renderer, Paint* cmpTarget, const RenderTransform* pTransform, RenderTransform* rTransform, RenderRegion& before)
+static Result _compFastTrack(RenderMethod* renderer, Paint* cmpTarget, const Matrix& pm, RenderRegion& before)
{
/* Access Shape class by Paint is bad... but it's ok still it's an internal usage. */
auto shape = static_cast<Shape*>(cmpTarget);
@@ -84,18 +84,17 @@ static Result _compFastTrack(RenderMethod* renderer, Paint* cmpTarget, const Ren
//nothing to clip
if (ptsCnt == 0) return Result::InvalidArguments;
-
if (ptsCnt != 4) return Result::InsufficientCondition;
- if (rTransform && (cmpTarget->pImpl->renderFlag & RenderUpdateFlag::Transform)) rTransform->update();
+ auto& rm = P(cmpTarget)->transform();
//No rotation and no skewing, still can try out clipping the rect region.
auto tryClip = false;
- if (pTransform && (!mathRightAngle(&pTransform->m) || mathSkewed(&pTransform->m))) tryClip = true;
- if (rTransform && (!mathRightAngle(&rTransform->m) || mathSkewed(&rTransform->m))) tryClip = true;
+ if ((!mathRightAngle(pm) || mathSkewed(pm))) tryClip = true;
+ if ((!mathRightAngle(rm) || mathSkewed(rm))) tryClip = true;
- if (tryClip) return _clipRect(renderer, pts, pTransform, rTransform, before);
+ if (tryClip) return _clipRect(renderer, pts, pm, rm, before);
//Perpendicular Rectangle?
auto pt1 = pts + 0;
@@ -110,16 +109,10 @@ static Result _compFastTrack(RenderMethod* renderer, Paint* cmpTarget, const Ren
auto v1 = *pt1;
auto v2 = *pt3;
-
- if (rTransform) {
- v1 *= rTransform->m;
- v2 *= rTransform->m;
- }
-
- if (pTransform) {
- v1 *= pTransform->m;
- v2 *= pTransform->m;
- }
+ v1 *= rm;
+ v2 *= rm;
+ v1 *= pm;
+ v2 *= pm;
//sorting
if (v1.x > v2.x) std::swap(v1.x, v2.x);
@@ -158,17 +151,15 @@ Iterator* Paint::Impl::iterator()
}
-Paint* Paint::Impl::duplicate()
+Paint* Paint::Impl::duplicate(Paint* ret)
{
- Paint* ret;
- PAINT_METHOD(ret, duplicate());
+ if (ret) ret->composite(nullptr, CompositeMethod::None);
+
+ PAINT_METHOD(ret, duplicate(ret));
//duplicate Transform
- if (rTransform) {
- ret->pImpl->rTransform = new RenderTransform();
- *ret->pImpl->rTransform = *rTransform;
- ret->pImpl->renderFlag |= RenderUpdateFlag::Transform;
- }
+ ret->pImpl->tr = tr;
+ ret->pImpl->renderFlag |= RenderUpdateFlag::Transform;
ret->pImpl->opacity = opacity;
@@ -180,14 +171,9 @@ Paint* Paint::Impl::duplicate()
bool Paint::Impl::rotate(float degree)
{
- if (rTransform) {
- if (rTransform->overriding) return false;
- if (mathEqual(degree, rTransform->degree)) return true;
- } else {
- if (mathZero(degree)) return true;
- rTransform = new RenderTransform();
- }
- rTransform->degree = degree;
+ if (tr.overriding) return false;
+ if (mathEqual(degree, tr.degree)) return true;
+ tr.degree = degree;
renderFlag |= RenderUpdateFlag::Transform;
return true;
@@ -196,14 +182,9 @@ bool Paint::Impl::rotate(float degree)
bool Paint::Impl::scale(float factor)
{
- if (rTransform) {
- if (rTransform->overriding) return false;
- if (mathEqual(factor, rTransform->scale)) return true;
- } else {
- if (mathEqual(factor, 1.0f)) return true;
- rTransform = new RenderTransform();
- }
- rTransform->scale = factor;
+ if (tr.overriding) return false;
+ if (mathEqual(factor, tr.scale)) return true;
+ tr.scale = factor;
renderFlag |= RenderUpdateFlag::Transform;
return true;
@@ -212,15 +193,10 @@ bool Paint::Impl::scale(float factor)
bool Paint::Impl::translate(float x, float y)
{
- if (rTransform) {
- if (rTransform->overriding) return false;
- if (mathEqual(x, rTransform->m.e13) && mathEqual(y, rTransform->m.e23)) return true;
- } else {
- if (mathZero(x) && mathZero(y)) return true;
- rTransform = new RenderTransform();
- }
- rTransform->m.e13 = x;
- rTransform->m.e23 = y;
+ if (tr.overriding) return false;
+ if (mathEqual(x, tr.m.e13) && mathEqual(y, tr.m.e23)) return true;
+ tr.m.e13 = x;
+ tr.m.e23 = y;
renderFlag |= RenderUpdateFlag::Transform;
return true;
@@ -229,6 +205,8 @@ bool Paint::Impl::translate(float x, float y)
bool Paint::Impl::render(RenderMethod* renderer)
{
+ if (opacity == 0) return true;
+
Compositor* cmp = nullptr;
/* Note: only ClipPath is processed in update() step.
@@ -247,8 +225,6 @@ bool Paint::Impl::render(RenderMethod* renderer)
if (cmp) renderer->beginComposite(cmp, compData->method, compData->target->pImpl->opacity);
- renderer->blend(blendMethod);
-
bool ret;
PAINT_METHOD(ret, render(renderer));
@@ -258,7 +234,7 @@ bool Paint::Impl::render(RenderMethod* renderer)
}
-RenderData Paint::Impl::update(RenderMethod* renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
+RenderData Paint::Impl::update(RenderMethod* renderer, const Matrix& pm, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
{
if (this->renderer != renderer) {
if (this->renderer) TVGERR("RENDERER", "paint's renderer has been changed!");
@@ -266,7 +242,7 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const RenderTransform* pT
this->renderer = renderer;
}
- if (renderFlag & RenderUpdateFlag::Transform) rTransform->update();
+ if (renderFlag & RenderUpdateFlag::Transform) tr.update();
/* 1. Composition Pre Processing */
RenderData trd = nullptr; //composite target render data
@@ -277,7 +253,7 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const RenderTransform* pT
if (compData) {
auto target = compData->target;
auto method = compData->method;
- target->pImpl->ctxFlag &= ~ContextFlag::FastTrack; //reset
+ P(target)->ctxFlag &= ~ContextFlag::FastTrack; //reset
/* If the transformation has no rotational factors and the ClipPath/Alpha(InvAlpha)Masking involves a simple rectangle,
we can optimize by using the viewport instead of the regular ClipPath/AlphaMasking sequence for improved performance. */
@@ -296,14 +272,14 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const RenderTransform* pT
}
if (tryFastTrack) {
viewport = renderer->viewport();
- if ((compFastTrack = _compFastTrack(renderer, target, pTransform, target->pImpl->rTransform, viewport)) == Result::Success) {
- target->pImpl->ctxFlag |= ContextFlag::FastTrack;
+ if ((compFastTrack = _compFastTrack(renderer, target, pm, viewport)) == Result::Success) {
+ P(target)->ctxFlag |= ContextFlag::FastTrack;
}
}
}
if (compFastTrack == Result::InsufficientCondition) {
childClipper = compData->method == CompositeMethod::ClipPath ? true : false;
- trd = target->pImpl->update(renderer, pTransform, clips, 255, pFlag, childClipper);
+ trd = P(target)->update(renderer, pm, clips, 255, pFlag, childClipper);
if (childClipper) clips.push(trd);
}
}
@@ -314,8 +290,9 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const RenderTransform* pT
opacity = MULTIPLY(opacity, this->opacity);
RenderData rd = nullptr;
- RenderTransform outTransform(pTransform, rTransform);
- PAINT_METHOD(rd, update(renderer, &outTransform, clips, opacity, newFlag, clipper));
+
+ tr.cm = pm * tr.m;
+ PAINT_METHOD(rd, update(renderer, tr.cm, clips, opacity, newFlag, clipper));
/* 3. Composition Post Processing */
if (compFastTrack == Result::Success) renderer->viewport(viewport);
@@ -325,13 +302,13 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const RenderTransform* pT
}
-bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking)
+bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking, bool origin)
{
- Matrix* m = nullptr;
bool ret;
+ const auto& m = this->transform(origin);
//Case: No transformed, quick return!
- if (!transformed || !(m = this->transform())) {
+ if (!transformed || mathIdentity(&m)) {
PAINT_METHOD(ret, bounds(x, y, w, h, stroking));
return ret;
}
@@ -355,7 +332,7 @@ bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transforme
//Compute the AABB after transformation
for (int i = 0; i < 4; i++) {
- pt[i] *= *m;
+ pt[i] *= m;
if (pt[i].x < x1) x1 = pt[i].x;
if (pt[i].x > x2) x2 = pt[i].x;
@@ -372,6 +349,26 @@ bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transforme
}
+void Paint::Impl::reset()
+{
+ if (compData) {
+ if (P(compData->target)->unref() == 0) delete(compData->target);
+ free(compData);
+ compData = nullptr;
+ }
+ mathIdentity(&tr.m);
+ tr.degree = 0.0f;
+ tr.scale = 1.0f;
+ tr.overriding = false;
+
+ blendMethod = BlendMethod::Normal;
+ renderFlag = RenderUpdateFlag::None;
+ ctxFlag = ContextFlag::Invalid;
+ opacity = 255;
+ paint->id = 0;
+}
+
+
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
@@ -417,9 +414,7 @@ Result Paint::transform(const Matrix& m) noexcept
Matrix Paint::transform() noexcept
{
- auto pTransform = pImpl->transform();
- if (pTransform) return *pTransform;
- return {1, 0, 0, 0, 1, 0, 0, 0, 1};
+ return pImpl->transform();
}
@@ -429,9 +424,9 @@ TVG_DEPRECATED Result Paint::bounds(float* x, float* y, float* w, float* h) cons
}
-Result Paint::bounds(float* x, float* y, float* w, float* h, bool transform) const noexcept
+Result Paint::bounds(float* x, float* y, float* w, float* h, bool transformed) const noexcept
{
- if (pImpl->bounds(x, y, w, h, transform, true)) return Result::Success;
+ if (pImpl->bounds(x, y, w, h, transformed, true, transformed)) return Result::Success;
return Result::InsufficientCondition;
}
@@ -444,6 +439,11 @@ Paint* Paint::duplicate() const noexcept
Result Paint::composite(std::unique_ptr<Paint> target, CompositeMethod method) noexcept
{
+ if (method == CompositeMethod::ClipPath && target && target->identifier() != TVG_CLASS_ID_SHAPE) {
+ TVGERR("RENDERER", "ClipPath only allows the Shape!");
+ return Result::NonSupport;
+ }
+
auto p = target.release();
if (pImpl->composite(this, p, method)) return Result::Success;
delete(p);
@@ -486,7 +486,7 @@ uint32_t Paint::identifier() const noexcept
}
-Result Paint::blend(BlendMethod method) const noexcept
+Result Paint::blend(BlendMethod method) noexcept
{
if (pImpl->blendMethod != method) {
pImpl->blendMethod = method;
diff --git a/thirdparty/thorvg/src/renderer/tvgPaint.h b/thirdparty/thorvg/src/renderer/tvgPaint.h
index bc07ab52ab..e43ca239bb 100644
--- a/thirdparty/thorvg/src/renderer/tvgPaint.h
+++ b/thirdparty/thorvg/src/renderer/tvgPaint.h
@@ -48,17 +48,40 @@ namespace tvg
struct Paint::Impl
{
Paint* paint = nullptr;
- RenderTransform* rTransform = nullptr;
Composite* compData = nullptr;
RenderMethod* renderer = nullptr;
- BlendMethod blendMethod = BlendMethod::Normal; //uint8_t
- uint8_t renderFlag = RenderUpdateFlag::None;
- uint8_t ctxFlag = ContextFlag::Invalid;
- uint8_t id;
- uint8_t opacity = 255;
+ struct {
+ Matrix m; //input matrix
+ Matrix cm; //multipled parents matrix
+ float degree; //rotation degree
+ float scale; //scale factor
+ bool overriding; //user transform?
+
+ void update()
+ {
+ if (overriding) return;
+ m.e11 = 1.0f;
+ m.e12 = 0.0f;
+ m.e21 = 0.0f;
+ m.e22 = 1.0f;
+ m.e31 = 0.0f;
+ m.e32 = 0.0f;
+ m.e33 = 1.0f;
+ mathScale(&m, scale, scale);
+ mathRotate(&m, degree);
+ }
+ } tr;
+ BlendMethod blendMethod;
+ uint8_t renderFlag;
+ uint8_t ctxFlag;
+ uint8_t opacity;
uint8_t refCnt = 0; //reference count
+ uint8_t id; //TODO: deprecated, remove it
- Impl(Paint* pnt) : paint(pnt) {}
+ Impl(Paint* pnt) : paint(pnt)
+ {
+ reset();
+ }
~Impl()
{
@@ -66,7 +89,6 @@ namespace tvg
if (P(compData->target)->unref() == 0) delete(compData->target);
free(compData);
}
- delete(rTransform);
if (renderer && (renderer->unref() == 0)) delete(renderer);
}
@@ -84,23 +106,19 @@ namespace tvg
bool transform(const Matrix& m)
{
- if (!rTransform) {
- if (mathIdentity(&m)) return true;
- rTransform = new RenderTransform();
- }
- rTransform->override(m);
+ tr.m = m;
+ tr.overriding = true;
renderFlag |= RenderUpdateFlag::Transform;
return true;
}
- Matrix* transform()
+ Matrix& transform(bool origin = false)
{
- if (rTransform) {
- if (renderFlag & RenderUpdateFlag::Transform) rTransform->update();
- return &rTransform->m;
- }
- return nullptr;
+ //update transform
+ if (renderFlag & RenderUpdateFlag::Transform) tr.update();
+ if (origin) return tr.cm;
+ return tr.m;
}
bool composite(Paint* source, Paint* target, CompositeMethod method)
@@ -135,10 +153,11 @@ namespace tvg
bool rotate(float degree);
bool scale(float factor);
bool translate(float x, float y);
- bool bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking);
- RenderData update(RenderMethod* renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper = false);
+ bool bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking, bool origin = false);
+ RenderData update(RenderMethod* renderer, const Matrix& pm, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper = false);
bool render(RenderMethod* renderer);
- Paint* duplicate();
+ Paint* duplicate(Paint* ret = nullptr);
+ void reset();
};
}
diff --git a/thirdparty/thorvg/src/renderer/tvgPicture.cpp b/thirdparty/thorvg/src/renderer/tvgPicture.cpp
index 5bd55a3f7b..e6653b1c99 100644
--- a/thirdparty/thorvg/src/renderer/tvgPicture.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgPicture.cpp
@@ -73,6 +73,8 @@ bool Picture::Impl::needComposition(uint8_t opacity)
bool Picture::Impl::render(RenderMethod* renderer)
{
bool ret = false;
+ renderer->blend(picture->blend(), true);
+
if (surface) return renderer->renderImage(rd);
else if (paint) {
Compositor* cmp = nullptr;
@@ -104,21 +106,6 @@ RenderRegion Picture::Impl::bounds(RenderMethod* renderer)
}
-RenderTransform Picture::Impl::resizeTransform(const RenderTransform* pTransform)
-{
- //Overriding Transformation by the desired image size
- auto sx = w / loader->w;
- auto sy = h / loader->h;
- auto scale = sx < sy ? sx : sy;
-
- RenderTransform tmp;
- tmp.m = {scale, 0, 0, 0, scale, 0, 0, 0, 1};
-
- if (!pTransform) return tmp;
- else return RenderTransform(pTransform, &tmp);
-}
-
-
Result Picture::Impl::load(ImageLoader* loader)
{
//Same resource has been loaded.
@@ -215,18 +202,24 @@ Result Picture::size(float* w, float* h) const noexcept
}
-Result Picture::mesh(const Polygon* triangles, uint32_t triangleCnt) noexcept
+const Paint* Picture::paint(uint32_t id) noexcept
{
- if (!triangles && triangleCnt > 0) return Result::InvalidArguments;
- if (triangles && triangleCnt == 0) return Result::InvalidArguments;
-
- pImpl->mesh(triangles, triangleCnt);
- return Result::Success;
-}
+ struct Value
+ {
+ uint32_t id;
+ const Paint* ret;
+ } value = {id, nullptr};
+ auto cb = [](const tvg::Paint* paint, void* data) -> bool
+ {
+ auto p = static_cast<Value*>(data);
+ if (p->id == paint->id) {
+ p->ret = paint;
+ return false;
+ }
+ return true;
+ };
-uint32_t Picture::mesh(const Polygon** triangles) const noexcept
-{
- if (triangles) *triangles = pImpl->rm.triangles;
- return pImpl->rm.triangleCnt;
+ tvg::Accessor::gen()->set(this, cb, &value);
+ return value.ret;
}
diff --git a/thirdparty/thorvg/src/renderer/tvgPicture.h b/thirdparty/thorvg/src/renderer/tvgPicture.h
index bd7021218a..3a4880caba 100644
--- a/thirdparty/thorvg/src/renderer/tvgPicture.h
+++ b/thirdparty/thorvg/src/renderer/tvgPicture.h
@@ -63,12 +63,10 @@ struct Picture::Impl
Surface* surface = nullptr; //bitmap picture uses
RenderData rd = nullptr; //engine data
float w = 0, h = 0;
- RenderMesh rm; //mesh data
Picture* picture = nullptr;
bool resizing = false;
bool needComp = false; //need composition
- RenderTransform resizeTransform(const RenderTransform* pTransform);
bool needComposition(uint8_t opacity);
bool render(RenderMethod* renderer);
bool size(float w, float h);
@@ -90,58 +88,37 @@ struct Picture::Impl
delete(paint);
}
- RenderData update(RenderMethod* renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
+ RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, TVG_UNUSED bool clipper)
{
auto flag = static_cast<RenderUpdateFlag>(pFlag | load());
if (surface) {
if (flag == RenderUpdateFlag::None) return rd;
- auto transform = resizeTransform(pTransform);
- rd = renderer->prepare(surface, &rm, rd, &transform, clips, opacity, flag);
+
+ //Overriding Transformation by the desired image size
+ auto sx = w / loader->w;
+ auto sy = h / loader->h;
+ auto scale = sx < sy ? sx : sy;
+ auto m = transform * Matrix{scale, 0, 0, 0, scale, 0, 0, 0, 1};
+
+ rd = renderer->prepare(surface, rd, m, clips, opacity, flag);
} else if (paint) {
if (resizing) {
loader->resize(paint, w, h);
resizing = false;
}
needComp = needComposition(opacity) ? true : false;
- rd = paint->pImpl->update(renderer, pTransform, clips, opacity, flag, clipper);
+ rd = paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
}
return rd;
}
bool bounds(float* x, float* y, float* w, float* h, bool stroking)
{
- if (rm.triangleCnt > 0) {
- auto triangles = rm.triangles;
- auto min = triangles[0].vertex[0].pt;
- auto max = triangles[0].vertex[0].pt;
-
- for (uint32_t i = 0; i < rm.triangleCnt; ++i) {
- if (triangles[i].vertex[0].pt.x < min.x) min.x = triangles[i].vertex[0].pt.x;
- else if (triangles[i].vertex[0].pt.x > max.x) max.x = triangles[i].vertex[0].pt.x;
- if (triangles[i].vertex[0].pt.y < min.y) min.y = triangles[i].vertex[0].pt.y;
- else if (triangles[i].vertex[0].pt.y > max.y) max.y = triangles[i].vertex[0].pt.y;
-
- if (triangles[i].vertex[1].pt.x < min.x) min.x = triangles[i].vertex[1].pt.x;
- else if (triangles[i].vertex[1].pt.x > max.x) max.x = triangles[i].vertex[1].pt.x;
- if (triangles[i].vertex[1].pt.y < min.y) min.y = triangles[i].vertex[1].pt.y;
- else if (triangles[i].vertex[1].pt.y > max.y) max.y = triangles[i].vertex[1].pt.y;
-
- if (triangles[i].vertex[2].pt.x < min.x) min.x = triangles[i].vertex[2].pt.x;
- else if (triangles[i].vertex[2].pt.x > max.x) max.x = triangles[i].vertex[2].pt.x;
- if (triangles[i].vertex[2].pt.y < min.y) min.y = triangles[i].vertex[2].pt.y;
- else if (triangles[i].vertex[2].pt.y > max.y) max.y = triangles[i].vertex[2].pt.y;
- }
- if (x) *x = min.x;
- if (y) *y = min.y;
- if (w) *w = max.x - min.x;
- if (h) *h = max.y - min.y;
- } else {
- if (x) *x = 0;
- if (y) *y = 0;
- if (w) *w = this->w;
- if (h) *h = this->h;
- }
+ if (x) *x = 0;
+ if (y) *y = 0;
+ if (w) *w = this->w;
+ if (h) *h = this->h;
return true;
}
@@ -176,32 +153,21 @@ struct Picture::Impl
return load(loader);
}
- void mesh(const Polygon* triangles, const uint32_t triangleCnt)
+ Paint* duplicate(Paint* ret)
{
- if (triangles && triangleCnt > 0) {
- this->rm.triangleCnt = triangleCnt;
- this->rm.triangles = (Polygon*)malloc(sizeof(Polygon) * triangleCnt);
- memcpy(this->rm.triangles, triangles, sizeof(Polygon) * triangleCnt);
- } else {
- free(this->rm.triangles);
- this->rm.triangles = nullptr;
- this->rm.triangleCnt = 0;
- }
- }
+ if (ret) TVGERR("RENDERER", "TODO: duplicate()");
- Paint* duplicate()
- {
load();
- auto ret = Picture::gen().release();
- auto dup = ret->pImpl;
+ auto picture = Picture::gen().release();
+ auto dup = picture->pImpl;
if (paint) dup->paint = paint->duplicate();
if (loader) {
dup->loader = loader;
++dup->loader->sharing;
- PP(ret)->renderFlag |= RenderUpdateFlag::Image;
+ PP(picture)->renderFlag |= RenderUpdateFlag::Image;
}
dup->surface = surface;
@@ -209,13 +175,7 @@ struct Picture::Impl
dup->h = h;
dup->resizing = resizing;
- if (rm.triangleCnt > 0) {
- dup->rm.triangleCnt = rm.triangleCnt;
- dup->rm.triangles = (Polygon*)malloc(sizeof(Polygon) * rm.triangleCnt);
- memcpy(dup->rm.triangles, rm.triangles, sizeof(Polygon) * rm.triangleCnt);
- }
-
- return ret;
+ return picture;
}
Iterator* iterator()
diff --git a/thirdparty/thorvg/src/renderer/tvgRender.cpp b/thirdparty/thorvg/src/renderer/tvgRender.cpp
index 82145b9aa4..8ee76493c2 100644
--- a/thirdparty/thorvg/src/renderer/tvgRender.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgRender.cpp
@@ -46,41 +46,6 @@ uint32_t RenderMethod::unref()
}
-void RenderTransform::override(const Matrix& m)
-{
- this->m = m;
- overriding = true;
-}
-
-
-void RenderTransform::update()
-{
- if (overriding) return;
-
- m.e11 = 1.0f;
- m.e12 = 0.0f;
-
- m.e21 = 0.0f;
- m.e22 = 1.0f;
-
- m.e31 = 0.0f;
- m.e32 = 0.0f;
- m.e33 = 1.0f;
-
- mathScale(&m, scale, scale);
- mathRotate(&m, degree);
-}
-
-
-RenderTransform::RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs)
-{
- if (lhs && rhs) m = lhs->m * rhs->m;
- else if (lhs) m = lhs->m;
- else if (rhs) m = rhs->m;
- else mathIdentity(&m);
-}
-
-
void RenderRegion::intersect(const RenderRegion& rhs)
{
auto x1 = x + w;
diff --git a/thirdparty/thorvg/src/renderer/tvgRender.h b/thirdparty/thorvg/src/renderer/tvgRender.h
index ff55748033..f1042884ec 100644
--- a/thirdparty/thorvg/src/renderer/tvgRender.h
+++ b/thirdparty/thorvg/src/renderer/tvgRender.h
@@ -23,6 +23,7 @@
#ifndef _TVG_RENDER_H_
#define _TVG_RENDER_H_
+#include <math.h>
#include "tvgCommon.h"
#include "tvgArray.h"
#include "tvgLock.h"
@@ -85,15 +86,15 @@ struct Compositor
uint8_t opacity;
};
-struct RenderMesh
+struct Vertex
{
- Polygon* triangles = nullptr;
- uint32_t triangleCnt = 0;
+ Point pt;
+ Point uv;
+};
- ~RenderMesh()
- {
- free(triangles);
- }
+struct Polygon
+{
+ Vertex vertex[3];
};
struct RenderRegion
@@ -110,24 +111,6 @@ struct RenderRegion
}
};
-struct RenderTransform
-{
- Matrix m;
- float degree = 0.0f; //rotation degree
- float scale = 1.0f; //scale factor
- bool overriding = false; //user transform?
-
- void update();
- void override(const Matrix& m);
-
- RenderTransform()
- {
- m.e13 = m.e23 = 0.0f;
- }
-
- RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs);
-};
-
struct RenderStroke
{
float width = 0.0f;
@@ -147,6 +130,57 @@ struct RenderStroke
bool simultaneous = true;
} trim;
+ void operator=(const RenderStroke& rhs)
+ {
+ width = rhs.width;
+
+ memcpy(color, rhs.color, sizeof(color));
+
+ delete(fill);
+ if (rhs.fill) fill = rhs.fill->duplicate();
+ else fill = nullptr;
+
+ free(dashPattern);
+ if (rhs.dashCnt > 0) {
+ dashPattern = static_cast<float*>(malloc(sizeof(float) * rhs.dashCnt));
+ memcpy(dashPattern, rhs.dashPattern, sizeof(float) * rhs.dashCnt);
+ } else {
+ dashPattern = nullptr;
+ }
+ dashCnt = rhs.dashCnt;
+ miterlimit = rhs.miterlimit;
+ cap = rhs.cap;
+ join = rhs.join;
+ strokeFirst = rhs.strokeFirst;
+ trim = rhs.trim;
+ }
+
+ bool strokeTrim(float& begin, float& end) const
+ {
+ begin = trim.begin;
+ end = trim.end;
+
+ if (fabsf(end - begin) >= 1.0f) {
+ begin = 0.0f;
+ end = 1.0f;
+ return false;
+ }
+
+ auto loop = true;
+
+ if (begin > 1.0f && end > 1.0f) loop = false;
+ if (begin < 0.0f && end < 0.0f) loop = false;
+ if (begin >= 0.0f && begin <= 1.0f && end >= 0.0f && end <= 1.0f) loop = false;
+
+ if (begin > 1.0f) begin -= 1.0f;
+ if (begin < 0.0f) begin += 1.0f;
+ if (end > 1.0f) end -= 1.0f;
+ if (end < 0.0f) end += 1.0f;
+
+ if ((loop && begin < end) || (!loop && begin > end)) std::swap(begin, end);
+ return true;
+ }
+
~RenderStroke()
{
free(dashPattern);
@@ -191,7 +225,7 @@ struct RenderShape
{
if (!stroke) return false;
if (stroke->trim.begin == 0.0f && stroke->trim.end == 1.0f) return false;
- if (stroke->trim.begin == 1.0f && stroke->trim.end == 0.0f) return false;
+ if (fabsf(stroke->trim.end - stroke->trim.begin) >= 1.0f) return false;
return true;
}
@@ -252,9 +286,8 @@ public:
uint32_t unref();
virtual ~RenderMethod() {}
- virtual RenderData prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) = 0;
- virtual RenderData prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) = 0;
- virtual RenderData prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) = 0;
+ virtual RenderData prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) = 0;
+ virtual RenderData prepare(Surface* surface, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) = 0;
virtual bool preRender() = 0;
virtual bool renderShape(RenderData data) = 0;
virtual bool renderImage(RenderData data) = 0;
@@ -263,7 +296,7 @@ public:
virtual RenderRegion region(RenderData data) = 0;
virtual RenderRegion viewport() = 0;
virtual bool viewport(const RenderRegion& vp) = 0;
- virtual bool blend(BlendMethod method) = 0;
+ virtual bool blend(BlendMethod method, bool direct = false) = 0;
virtual ColorSpace colorSpace() = 0;
virtual const Surface* mainSurface() = 0;
@@ -288,6 +321,8 @@ static inline bool MASK_REGION_MERGING(CompositeMethod method)
//these might expand the rendering region
case CompositeMethod::AddMask:
case CompositeMethod::DifferenceMask:
+ case CompositeMethod::LightenMask:
+ case CompositeMethod::DarkenMask:
return true;
default:
TVGERR("RENDERER", "Unsupported Composite Method! = %d", (int)method);
@@ -321,6 +356,8 @@ static inline ColorSpace COMPOSITE_TO_COLORSPACE(RenderMethod* renderer, Composi
case CompositeMethod::DifferenceMask:
case CompositeMethod::SubtractMask:
case CompositeMethod::IntersectMask:
+ case CompositeMethod::LightenMask:
+ case CompositeMethod::DarkenMask:
return ColorSpace::Grayscale8;
//TODO: Optimize Luma/InvLuma colorspace to Grayscale8
case CompositeMethod::LumaMask:
diff --git a/thirdparty/thorvg/src/renderer/tvgScene.h b/thirdparty/thorvg/src/renderer/tvgScene.h
index 8b1981edfa..190ecd31b9 100644
--- a/thirdparty/thorvg/src/renderer/tvgScene.h
+++ b/thirdparty/thorvg/src/renderer/tvgScene.h
@@ -101,7 +101,7 @@ struct Scene::Impl
return true;
}
- RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flag, bool clipper)
+ RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flag, TVG_UNUSED bool clipper)
{
if ((needComp = needComposition(opacity))) {
/* Overriding opacity value. If this scene is half-translucent,
@@ -109,20 +109,10 @@ struct Scene::Impl
this->opacity = opacity;
opacity = 255;
}
-
- if (clipper) {
- Array<RenderData> rds(paints.size());
- for (auto paint : paints) {
- rds.push(paint->pImpl->update(renderer, transform, clips, opacity, flag, true));
- }
- rd = renderer->prepare(rds, rd, transform, clips, opacity, flag);
- return rd;
- } else {
- for (auto paint : paints) {
- paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
- }
- return nullptr;
+ for (auto paint : paints) {
+ paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
}
+ return nullptr;
}
bool render(RenderMethod* renderer)
@@ -130,6 +120,8 @@ struct Scene::Impl
Compositor* cmp = nullptr;
auto ret = true;
+ renderer->blend(scene->blend());
+
if (needComp) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
renderer->beginComposite(cmp, CompositeMethod::None, opacity);
@@ -198,10 +190,12 @@ struct Scene::Impl
return true;
}
- Paint* duplicate()
+ Paint* duplicate(Paint* ret)
{
- auto ret = Scene::gen().release();
- auto dup = ret->pImpl;
+ if (ret) TVGERR("RENDERER", "TODO: duplicate()");
+
+ auto scene = Scene::gen().release();
+ auto dup = scene->pImpl;
for (auto paint : paints) {
auto cdup = paint->duplicate();
@@ -209,7 +203,7 @@ struct Scene::Impl
dup->paints.push_back(cdup);
}
- return ret;
+ return scene;
}
void clear(bool free)
diff --git a/thirdparty/thorvg/src/renderer/tvgShape.h b/thirdparty/thorvg/src/renderer/tvgShape.h
index ebc0b304ab..94704aee67 100644
--- a/thirdparty/thorvg/src/renderer/tvgShape.h
+++ b/thirdparty/thorvg/src/renderer/tvgShape.h
@@ -34,6 +34,7 @@ struct Shape::Impl
RenderData rd = nullptr; //engine data
Shape* shape;
uint8_t flag = RenderUpdateFlag::None;
+
uint8_t opacity; //for composition
bool needComp = false; //composite or not
@@ -53,10 +54,13 @@ struct Shape::Impl
Compositor* cmp = nullptr;
bool ret;
+ renderer->blend(shape->blend(), !needComp);
+
if (needComp) {
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
renderer->beginComposite(cmp, CompositeMethod::None, opacity);
}
+
ret = renderer->renderShape(rd);
if (cmp) renderer->endComposite(cmp);
return ret;
@@ -95,7 +99,7 @@ struct Shape::Impl
return true;
}
- RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
+ RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
{
if (static_cast<RenderUpdateFlag>(pFlag | flag) == RenderUpdateFlag::None) return rd;
@@ -216,23 +220,6 @@ struct Shape::Impl
if (mathEqual(rs.stroke->trim.begin, begin) && mathEqual(rs.stroke->trim.end, end) &&
rs.stroke->trim.simultaneous == simultaneous) return;
- auto loop = true;
-
- if (begin > 1.0f && end > 1.0f) loop = false;
- if (begin < 0.0f && end < 0.0f) loop = false;
- if (begin >= 0.0f && begin <= 1.0f && end >= 0.0f && end <= 1.0f) loop = false;
-
- if (begin > 1.0f) begin -= 1.0f;
- if (begin < 0.0f) begin += 1.0f;
- if (end > 1.0f) end -= 1.0f;
- if (end < 0.0f) end += 1.0f;
-
- if ((loop && begin < end) || (!loop && begin > end)) {
- auto tmp = begin;
- begin = end;
- end = tmp;
- }
-
rs.stroke->trim.begin = begin;
rs.stroke->trim.end = end;
rs.stroke->trim.simultaneous = simultaneous;
@@ -359,47 +346,56 @@ struct Shape::Impl
this->flag |= flag;
}
- Paint* duplicate()
+ Paint* duplicate(Paint* ret)
{
- auto ret = Shape::gen().release();
- auto dup = ret->pImpl;
+ auto shape = static_cast<Shape*>(ret);
+ if (shape) shape->reset();
+ else shape = Shape::gen().release();
+
+ auto dup = shape->pImpl;
+ delete(dup->rs.fill);
+ //Default Properties
+ dup->flag = RenderUpdateFlag::All;
dup->rs.rule = rs.rule;
//Color
memcpy(dup->rs.color, rs.color, sizeof(rs.color));
- dup->flag = RenderUpdateFlag::Color;
//Path
- if (rs.path.cmds.count > 0 && rs.path.pts.count > 0) {
- dup->rs.path.cmds = rs.path.cmds;
- dup->rs.path.pts = rs.path.pts;
- dup->flag |= RenderUpdateFlag::Path;
- }
+ dup->rs.path.cmds.push(rs.path.cmds);
+ dup->rs.path.pts.push(rs.path.pts);
//Stroke
if (rs.stroke) {
- dup->rs.stroke = new RenderStroke();
+ if (!dup->rs.stroke) dup->rs.stroke = new RenderStroke;
*dup->rs.stroke = *rs.stroke;
- memcpy(dup->rs.stroke->color, rs.stroke->color, sizeof(rs.stroke->color));
- if (rs.stroke->dashCnt > 0) {
- dup->rs.stroke->dashPattern = static_cast<float*>(malloc(sizeof(float) * rs.stroke->dashCnt));
- memcpy(dup->rs.stroke->dashPattern, rs.stroke->dashPattern, sizeof(float) * rs.stroke->dashCnt);
- }
- if (rs.stroke->fill) {
- dup->rs.stroke->fill = rs.stroke->fill->duplicate();
- dup->flag |= RenderUpdateFlag::GradientStroke;
- }
- dup->flag |= RenderUpdateFlag::Stroke;
+ } else {
+ delete(dup->rs.stroke);
+ dup->rs.stroke = nullptr;
}
//Fill
- if (rs.fill) {
- dup->rs.fill = rs.fill->duplicate();
- dup->flag |= RenderUpdateFlag::Gradient;
- }
+ if (rs.fill) dup->rs.fill = rs.fill->duplicate();
+ else dup->rs.fill = nullptr;
- return ret;
+ return shape;
+ }
+
+ void reset()
+ {
+ PP(shape)->reset();
+ rs.path.cmds.clear();
+ rs.path.pts.clear();
+
+ rs.color[3] = 0;
+ rs.rule = FillRule::Winding;
+
+ delete(rs.stroke);
+ rs.stroke = nullptr;
+
+ delete(rs.fill);
+ rs.fill = nullptr;
}
Iterator* iterator()
diff --git a/thirdparty/thorvg/src/renderer/tvgTaskScheduler.h b/thirdparty/thorvg/src/renderer/tvgTaskScheduler.h
index 93f8481707..58918e88f0 100644
--- a/thirdparty/thorvg/src/renderer/tvgTaskScheduler.h
+++ b/thirdparty/thorvg/src/renderer/tvgTaskScheduler.h
@@ -23,8 +23,6 @@
#ifndef _TVG_TASK_SCHEDULER_H_
#define _TVG_TASK_SCHEDULER_H_
-#define _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR
-
#include <mutex>
#include <condition_variable>
diff --git a/thirdparty/thorvg/src/renderer/tvgText.cpp b/thirdparty/thorvg/src/renderer/tvgText.cpp
index 4b5eb35ce5..d54c78783c 100644
--- a/thirdparty/thorvg/src/renderer/tvgText.cpp
+++ b/thirdparty/thorvg/src/renderer/tvgText.cpp
@@ -35,7 +35,7 @@
/************************************************************************/
-Text::Text() : pImpl(new Impl)
+Text::Text() : pImpl(new Impl(this))
{
Paint::pImpl->id = TVG_CLASS_ID_TEXT;
}
@@ -95,20 +95,13 @@ Result Text::unload(const std::string& path) noexcept
Result Text::fill(uint8_t r, uint8_t g, uint8_t b) noexcept
{
- if (!pImpl->paint) return Result::InsufficientCondition;
-
- return pImpl->fill(r, g, b);
+ return pImpl->shape->fill(r, g, b);
}
Result Text::fill(unique_ptr<Fill> f) noexcept
{
- if (!pImpl->paint) return Result::InsufficientCondition;
-
- auto p = f.release();
- if (!p) return Result::MemoryCorruption;
-
- return pImpl->fill(p);
+ return pImpl->shape->fill(std::move(f));
}
diff --git a/thirdparty/thorvg/src/renderer/tvgText.h b/thirdparty/thorvg/src/renderer/tvgText.h
index c56ce8b878..746b85bea6 100644
--- a/thirdparty/thorvg/src/renderer/tvgText.h
+++ b/thirdparty/thorvg/src/renderer/tvgText.h
@@ -26,37 +26,27 @@
#include <cstring>
#include "tvgShape.h"
#include "tvgFill.h"
-
-#ifdef THORVG_TTF_LOADER_SUPPORT
- #include "tvgTtfLoader.h"
-#else
- #include "tvgLoader.h"
-#endif
+#include "tvgLoader.h"
struct Text::Impl
{
FontLoader* loader = nullptr;
- Shape* paint = nullptr;
+ Text* paint;
+ Shape* shape;
char* utf8 = nullptr;
float fontSize;
bool italic = false;
bool changed = false;
- ~Impl()
- {
- free(utf8);
- LoaderMgr::retrieve(loader);
- delete(paint);
- }
-
- Result fill(uint8_t r, uint8_t g, uint8_t b)
+ Impl(Text* p) : paint(p), shape(Shape::gen().release())
{
- return paint->fill(r, g, b);
}
- Result fill(Fill* f)
+ ~Impl()
{
- return paint->fill(cast<Fill>(f));
+ free(utf8);
+ LoaderMgr::retrieve(loader);
+ delete(shape);
}
Result text(const char* utf8)
@@ -74,6 +64,11 @@ struct Text::Impl
auto loader = LoaderMgr::loader(name);
if (!loader) return Result::InsufficientCondition;
+ if (style && strstr(style, "italic")) italic = true;
+ else italic = false;
+
+ fontSize = size;
+
//Same resource has been loaded.
if (this->loader == loader) {
this->loader->sharing--; //make it sure the reference counting.
@@ -83,50 +78,41 @@ struct Text::Impl
}
this->loader = static_cast<FontLoader*>(loader);
- if (!paint) paint = Shape::gen().release();
-
- fontSize = size;
- if (style && strstr(style, "italic")) italic = true;
changed = true;
return Result::Success;
}
RenderRegion bounds(RenderMethod* renderer)
{
- if (paint) return P(paint)->bounds(renderer);
- else return {0, 0, 0, 0};
+ return P(shape)->bounds(renderer);
}
bool render(RenderMethod* renderer)
{
- if (paint) return PP(paint)->render(renderer);
- return true;
+ renderer->blend(paint->blend(), true);
+ return PP(shape)->render(renderer);
}
bool load()
{
if (!loader) return false;
+ loader->request(shape, utf8);
//reload
if (changed) {
- loader->request(paint, utf8, italic);
loader->read();
changed = false;
}
- if (paint) {
- loader->resize(paint, fontSize, fontSize);
- return true;
- }
- return false;
+ return loader->transform(shape, fontSize, italic);
}
- RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
+ RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, TVG_UNUSED bool clipper)
{
if (!load()) return nullptr;
//transform the gradient coordinates based on the final scaled font.
- if (P(paint)->flag & RenderUpdateFlag::Gradient) {
- auto fill = P(paint)->rs.fill;
+ auto fill = P(shape)->rs.fill;
+ if (fill && P(shape)->flag & RenderUpdateFlag::Gradient) {
auto scale = 1.0f / loader->scale;
if (fill->identifier() == TVG_CLASS_ID_LINEAR) {
P(static_cast<LinearGradient*>(fill))->x1 *= scale;
@@ -142,23 +128,25 @@ struct Text::Impl
P(static_cast<RadialGradient*>(fill))->fr *= scale;
}
}
- return PP(paint)->update(renderer, transform, clips, opacity, pFlag, clipper);
+ return PP(shape)->update(renderer, transform, clips, opacity, pFlag, false);
}
bool bounds(float* x, float* y, float* w, float* h, TVG_UNUSED bool stroking)
{
- if (!load() || !paint) return false;
- paint->bounds(x, y, w, h, true);
+ if (!load()) return false;
+ PP(shape)->bounds(x, y, w, h, true, true, false);
return true;
}
- Paint* duplicate()
+ Paint* duplicate(Paint* ret)
{
+ if (ret) TVGERR("RENDERER", "TODO: duplicate()");
+
load();
- auto ret = Text::gen().release();
- auto dup = ret->pImpl;
- if (paint) dup->paint = static_cast<Shape*>(paint->duplicate());
+ auto text = Text::gen().release();
+ auto dup = text->pImpl;
+ P(shape)->duplicate(dup->shape);
if (loader) {
dup->loader = loader;
@@ -169,7 +157,7 @@ struct Text::Impl
dup->italic = italic;
dup->fontSize = fontSize;
- return ret;
+ return text;
}
Iterator* iterator()
diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh
index 1a68daf3c5..51dc156661 100755
--- a/thirdparty/thorvg/update-thorvg.sh
+++ b/thirdparty/thorvg/update-thorvg.sh
@@ -1,16 +1,22 @@
#!/bin/bash -e
-VERSION=0.14.2
+VERSION=0.14.8
+# Uncomment and set a git hash to use specific commit instead of tag.
+#GIT_COMMIT=
-cd thirdparty/thorvg/ || true
+pushd "$(dirname "$0")"
rm -rf AUTHORS LICENSE inc/ src/ *.zip *.tar.gz tmp/
mkdir tmp/ && pushd tmp/
# Release
-curl -L -O https://github.com/thorvg/thorvg/archive/v$VERSION.tar.gz
-# Current Github main branch tip
-#curl -L -O https://github.com/thorvg/thorvg/archive/refs/heads/main.tar.gz
+if [ ! -z "$GIT_COMMIT" ]; then
+ echo "Updating ThorVG to commit:" $GIT_COMMIT
+ curl -L -O https://github.com/thorvg/thorvg/archive/$GIT_COMMIT.tar.gz
+else
+ echo "Updating ThorVG to tagged release:" $VERSION
+ curl -L -O https://github.com/thorvg/thorvg/archive/v$VERSION.tar.gz
+fi
tar --strip-components=1 -xvf *.tar.gz
rm *.tar.gz
@@ -70,4 +76,4 @@ cp -rv src/loaders/jpg ../src/loaders/
popd
rm -rf tmp
-
+popd