summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--SConstruct43
-rw-r--r--core/config/project_settings.cpp5
-rw-r--r--core/input/input.cpp5
-rw-r--r--core/input/input_map.cpp4
-rw-r--r--core/io/logger.cpp2
-rw-r--r--core/io/packet_peer_udp.cpp2
-rw-r--r--core/io/resource_loader.cpp9
-rw-r--r--core/math/a_star_grid_2d.cpp4
-rw-r--r--core/object/class_db.cpp63
-rw-r--r--core/object/class_db.h1
-rw-r--r--core/variant/array.cpp8
-rw-r--r--core/variant/variant.h2
-rw-r--r--doc/classes/Animation.xml2
-rw-r--r--doc/classes/AnimationMixer.xml4
-rw-r--r--doc/classes/Array.xml539
-rw-r--r--doc/classes/EditorExportPlugin.xml8
-rw-r--r--doc/classes/EditorSettings.xml4
-rw-r--r--doc/classes/OS.xml2
-rw-r--r--doc/classes/PhysicsPointQueryParameters2D.xml1
-rw-r--r--doc/classes/PhysicsPointQueryParameters3D.xml1
-rw-r--r--doc/classes/PhysicsRayQueryParameters2D.xml1
-rw-r--r--doc/classes/PhysicsRayQueryParameters3D.xml1
-rw-r--r--doc/classes/PhysicsShapeQueryParameters2D.xml1
-rw-r--r--doc/classes/PhysicsShapeQueryParameters3D.xml1
-rw-r--r--doc/classes/PopupMenu.xml7
-rw-r--r--doc/classes/ProjectSettings.xml18
-rw-r--r--doc/classes/RichTextLabel.xml4
-rw-r--r--doc/classes/ScriptEditorBase.xml2
-rw-r--r--doc/classes/Transform2D.xml133
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.cpp4
-rw-r--r--drivers/gles3/storage/texture_storage.h2
-rw-r--r--editor/animation_bezier_editor.cpp42
-rw-r--r--editor/animation_bezier_editor.h3
-rw-r--r--editor/animation_track_editor.cpp45
-rw-r--r--editor/editor_dock_manager.cpp18
-rw-r--r--editor/editor_dock_manager.h2
-rw-r--r--editor/editor_file_system.cpp42
-rw-r--r--editor/editor_file_system.h2
-rw-r--r--editor/editor_help.cpp23
-rw-r--r--editor/editor_help.h2
-rw-r--r--editor/editor_log.cpp6
-rw-r--r--editor/editor_log.h2
-rw-r--r--editor/editor_node.cpp22
-rw-r--r--editor/editor_node.h3
-rw-r--r--editor/editor_settings.cpp6
-rw-r--r--editor/editor_settings_dialog.cpp15
-rw-r--r--editor/export/editor_export.cpp4
-rw-r--r--editor/export/editor_export_platform.cpp191
-rw-r--r--editor/gui/editor_bottom_panel.cpp6
-rw-r--r--editor/gui/editor_file_dialog.cpp1
-rw-r--r--editor/import/3d/resource_importer_obj.cpp8
-rw-r--r--editor/plugins/animation_library_editor.cpp22
-rw-r--r--editor/plugins/animation_library_editor.h1
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp21
-rw-r--r--editor/plugins/animation_player_editor_plugin.h2
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp3
-rw-r--r--editor/plugins/font_config_plugin.cpp47
-rw-r--r--editor/plugins/font_config_plugin.h2
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/polygon_2d_editor_plugin.cpp30
-rw-r--r--editor/plugins/script_editor_plugin.cpp2
-rw-r--r--editor/plugins/script_text_editor.cpp67
-rw-r--r--editor/plugins/tiles/tile_data_editors.cpp30
-rw-r--r--editor/plugins/tiles/tile_map_layer_editor.cpp11
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp5
-rw-r--r--editor/project_settings_editor.cpp15
-rw-r--r--editor/scene_tree_dock.cpp11
-rw-r--r--editor/themes/editor_theme_manager.cpp4
-rw-r--r--main/main.cpp29
-rw-r--r--methods.py10
-rw-r--r--misc/dist/html/editor.html1
-rw-r--r--misc/extension_api_validation/4.2-stable.expected7
-rw-r--r--modules/enet/doc_classes/ENetConnection.xml2
-rw-r--r--modules/gdscript/gdscript_editor.cpp13
-rw-r--r--modules/gdscript/gdscript_lambda_callable.cpp8
-rw-r--r--modules/gdscript/gdscript_lambda_callable.h1
-rw-r--r--modules/gdscript/gdscript_parser.cpp2
-rw-r--r--modules/gdscript/gdscript_vm.cpp19
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/lambda_bind_argument_count.gd18
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/lambda_bind_argument_count.out3
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/lambda_get_method.gd21
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/lambda_get_method.out5
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.out2
-rw-r--r--modules/gltf/doc_classes/GLTFAccessor.xml27
-rw-r--r--modules/gltf/editor/editor_import_blend_runner.cpp67
-rw-r--r--modules/gltf/editor/editor_import_blend_runner.h1
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp18
-rw-r--r--modules/gltf/gltf_document.cpp78
-rw-r--r--modules/gltf/gltf_document.h8
-rw-r--r--modules/gltf/structures/gltf_accessor.cpp28
-rw-r--r--modules/gltf/structures/gltf_accessor.h30
-rw-r--r--modules/jpg/image_loader_jpegd.cpp2
-rw-r--r--modules/openxr/openxr_api.h1
-rw-r--r--modules/text_server_adv/text_server_adv.cpp2
-rw-r--r--modules/text_server_fb/text_server_fb.cpp2
-rw-r--r--platform/android/java/app/AndroidManifest.xml4
-rw-r--r--platform/android/java/app/config.gradle2
-rw-r--r--platform/android/java/editor/build.gradle2
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt12
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt10
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt128
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java7
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotLib.java7
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java49
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt36
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java139
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java18
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt11
-rw-r--r--platform/android/java_godot_lib_jni.cpp7
-rw-r--r--platform/android/java_godot_lib_jni.h1
-rw-r--r--platform/ios/keyboard_input_view.mm30
-rw-r--r--platform/linuxbsd/joypad_linux.cpp6
-rw-r--r--platform/linuxbsd/joypad_linux.h15
-rw-r--r--platform/linuxbsd/wayland/wayland_thread.cpp5
-rw-r--r--platform/macos/display_server_macos.mm16
-rw-r--r--platform/macos/godot_main_macos.mm4
-rw-r--r--platform/macos/godot_menu_item.h1
-rw-r--r--platform/macos/godot_menu_item.mm14
-rw-r--r--platform/macos/native_menu_macos.mm37
-rw-r--r--platform/web/audio_driver_web.cpp32
-rw-r--r--platform/web/audio_driver_web.h1
-rw-r--r--platform/web/detect.py1
-rw-r--r--platform/web/godot_audio.h1
-rw-r--r--platform/web/js/libs/library_godot_audio.js27
-rw-r--r--platform/web/js/libs/library_godot_input.js1
-rw-r--r--platform/web/web_main.cpp4
-rw-r--r--platform/windows/display_server_windows.cpp11
-rw-r--r--platform/windows/display_server_windows.h5
-rw-r--r--platform/windows/native_menu_windows.cpp37
-rw-r--r--platform/windows/native_menu_windows.h1
-rw-r--r--scene/2d/animated_sprite_2d.cpp2
-rw-r--r--scene/2d/camera_2d.cpp6
-rw-r--r--scene/2d/camera_2d.h2
-rw-r--r--scene/2d/navigation_agent_2d.cpp3
-rw-r--r--scene/2d/navigation_agent_2d.h2
-rw-r--r--scene/2d/parallax_2d.cpp14
-rw-r--r--scene/2d/tile_map.cpp1
-rw-r--r--scene/3d/navigation_agent_3d.cpp3
-rw-r--r--scene/3d/navigation_agent_3d.h2
-rw-r--r--scene/3d/sprite_3d.cpp4
-rw-r--r--scene/audio/audio_stream_player_internal.cpp1
-rw-r--r--scene/gui/control.cpp20
-rw-r--r--scene/gui/file_dialog.cpp1
-rw-r--r--scene/gui/graph_edit.cpp9
-rw-r--r--scene/gui/graph_frame.cpp4
-rw-r--r--scene/gui/graph_frame.h1
-rw-r--r--scene/gui/item_list.cpp1
-rw-r--r--scene/gui/menu_button.cpp1
-rw-r--r--scene/gui/option_button.cpp1
-rw-r--r--scene/gui/popup_menu.cpp12
-rw-r--r--scene/gui/popup_menu.h2
-rw-r--r--scene/gui/rich_text_label.cpp6
-rw-r--r--scene/gui/tab_bar.cpp1
-rw-r--r--scene/gui/text_edit.cpp30
-rw-r--r--scene/gui/text_edit.h1
-rw-r--r--scene/main/node.cpp6
-rw-r--r--scene/property_list_helper.cpp19
-rw-r--r--scene/property_list_helper.h7
-rw-r--r--scene/resources/animation.cpp14
-rw-r--r--scene/resources/animation.h1
-rw-r--r--scene/resources/font.cpp1
-rw-r--r--scene/resources/packed_scene.cpp50
-rw-r--r--scene/resources/packed_scene.h1
-rw-r--r--servers/audio/audio_stream.cpp1
-rw-r--r--servers/audio/audio_stream.h1
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp22
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h2
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp30
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h2
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.cpp1
-rw-r--r--servers/rendering/renderer_rd/shaders/effects/ss_effects_downsample.glsl4
-rw-r--r--servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp16
-rw-r--r--servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h3
-rw-r--r--servers/rendering/rendering_device.cpp11
-rw-r--r--servers/rendering/shader_language.cpp22
-rw-r--r--servers/rendering/shader_language.h2
-rw-r--r--servers/rendering_server.cpp34
-rw-r--r--servers/rendering_server.h10
-rw-r--r--tests/core/math/test_basis.h18
-rw-r--r--tests/core/object/test_class_db.h6
-rw-r--r--thirdparty/README.md2
-rw-r--r--thirdparty/thorvg/inc/config.h2
-rw-r--r--thirdparty/thorvg/inc/thorvg.h2
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h1
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp78
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp48
-rw-r--r--thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp3
-rw-r--r--thirdparty/thorvg/src/renderer/tvgCanvas.h2
-rw-r--r--thirdparty/thorvg/src/renderer/tvgRender.h2
-rw-r--r--thirdparty/thorvg/src/renderer/tvgShape.h1
-rwxr-xr-xthirdparty/thorvg/update-thorvg.sh2
194 files changed, 2099 insertions, 1156 deletions
diff --git a/SConstruct b/SConstruct
index 3fabc4706f..f3331f3b0d 100644
--- a/SConstruct
+++ b/SConstruct
@@ -200,7 +200,10 @@ opts.Add(EnumVariable("arch", "CPU architecture", "auto", ["auto"] + architectur
opts.Add(BoolVariable("dev_build", "Developer build with dev-only debugging code (DEV_ENABLED)", False))
opts.Add(
EnumVariable(
- "optimize", "Optimization level", "speed_trace", ("none", "custom", "debug", "speed", "speed_trace", "size")
+ "optimize",
+ "Optimization level (by default inferred from 'target' and 'dev_build')",
+ "auto",
+ ("auto", "none", "custom", "debug", "speed", "speed_trace", "size"),
)
)
opts.Add(BoolVariable("debug_symbols", "Build with debugging symbols", False))
@@ -466,14 +469,15 @@ env.editor_build = env["target"] == "editor"
env.dev_build = env["dev_build"]
env.debug_features = env["target"] in ["editor", "template_debug"]
-if env.dev_build:
- opt_level = "none"
-elif env.debug_features:
- opt_level = "speed_trace"
-else: # Release
- opt_level = "speed"
+if env["optimize"] == "auto":
+ if env.dev_build:
+ opt_level = "none"
+ elif env.debug_features:
+ opt_level = "speed_trace"
+ else: # Release
+ opt_level = "speed"
+ env["optimize"] = ARGUMENTS.get("optimize", opt_level)
-env["optimize"] = ARGUMENTS.get("optimize", opt_level)
env["debug_symbols"] = methods.get_cmdline_bool("debug_symbols", env.dev_build)
if env.editor_build:
@@ -696,12 +700,11 @@ if env.msvc:
else:
env.Append(LINKFLAGS=["/DEBUG:NONE"])
- if env["optimize"] == "speed":
+ if env["optimize"].startswith("speed"):
env.Append(CCFLAGS=["/O2"])
env.Append(LINKFLAGS=["/OPT:REF"])
- elif env["optimize"] == "speed_trace":
- env.Append(CCFLAGS=["/O2"])
- env.Append(LINKFLAGS=["/OPT:REF", "/OPT:NOICF"])
+ if env["optimize"] == "speed_trace":
+ env.Append(LINKFLAGS=["/OPT:NOICF"])
elif env["optimize"] == "size":
env.Append(CCFLAGS=["/O1"])
env.Append(LINKFLAGS=["/OPT:REF"])
@@ -712,7 +715,13 @@ else:
# Adding dwarf-4 explicitly makes stacktraces work with clang builds,
# otherwise addr2line doesn't understand them
env.Append(CCFLAGS=["-gdwarf-4"])
- if env.dev_build:
+ if methods.using_emcc(env):
+ # Emscripten only produces dwarf symbols when using "-g3".
+ env.Append(CCFLAGS=["-g3"])
+ # Emscripten linker needs debug symbols options too.
+ env.Append(LINKFLAGS=["-gdwarf-4"])
+ env.Append(LINKFLAGS=["-g3"])
+ elif env.dev_build:
env.Append(CCFLAGS=["-g3"])
else:
env.Append(CCFLAGS=["-g2"])
@@ -727,17 +736,25 @@ else:
else:
env.Append(LINKFLAGS=["-s"])
+ # Linker needs optimization flags too, at least for Emscripten.
+ # For other toolchains, this _may_ be useful for LTO too to disambiguate.
+
if env["optimize"] == "speed":
env.Append(CCFLAGS=["-O3"])
+ env.Append(LINKFLAGS=["-O3"])
# `-O2` is friendlier to debuggers than `-O3`, leading to better crash backtraces.
elif env["optimize"] == "speed_trace":
env.Append(CCFLAGS=["-O2"])
+ env.Append(LINKFLAGS=["-O2"])
elif env["optimize"] == "size":
env.Append(CCFLAGS=["-Os"])
+ env.Append(LINKFLAGS=["-Os"])
elif env["optimize"] == "debug":
env.Append(CCFLAGS=["-Og"])
+ env.Append(LINKFLAGS=["-Og"])
elif env["optimize"] == "none":
env.Append(CCFLAGS=["-O0"])
+ env.Append(LINKFLAGS=["-O0"])
# Needs to happen after configure to handle "auto".
if env["lto"] != "none":
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index 768540a0fa..e59f79fcc8 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -329,9 +329,9 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) {
String path = p_value;
if (path.begins_with("*")) {
autoload.is_singleton = true;
- autoload.path = path.substr(1);
+ autoload.path = path.substr(1).simplify_path();
} else {
- autoload.path = path;
+ autoload.path = path.simplify_path();
}
add_autoload(autoload);
} else if (p_name.operator String().begins_with("global_group/")) {
@@ -1515,6 +1515,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/window/stretch/scale_mode", PROPERTY_HINT_ENUM, "fractional,integer"), "fractional");
GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/profiler/max_functions", PROPERTY_HINT_RANGE, "128,65535,1"), 16384);
+ GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "debug/settings/profiler/max_timestamp_query_elements", PROPERTY_HINT_RANGE, "256,65535,1"), 256);
GLOBAL_DEF(PropertyInfo(Variant::BOOL, "compression/formats/zstd/long_distance_matching"), Compression::zstd_long_distance_matching);
GLOBAL_DEF(PropertyInfo(Variant::INT, "compression/formats/zstd/compression_level", PROPERTY_HINT_RANGE, "1,22,1"), Compression::zstd_level);
diff --git a/core/input/input.cpp b/core/input/input.cpp
index 56f616fac4..ec0303df06 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -758,12 +758,13 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
bool was_pressed = action_state.cache.pressed;
_update_action_cache(E.key, action_state);
+ // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick.
if (action_state.cache.pressed && !was_pressed) {
- action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames();
+ action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1;
action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames();
}
if (!action_state.cache.pressed && was_pressed) {
- action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames();
+ action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1;
action_state.released_process_frame = Engine::get_singleton()->get_process_frames();
}
}
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp
index 178d02b987..ddeee9d765 100644
--- a/core/input/input_map.cpp
+++ b/core/input/input_map.cpp
@@ -636,6 +636,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::A | KeyModifierMask::CTRL));
inputs.push_back(InputEventKey::create_reference(Key::LEFT | KeyModifierMask::CMD_OR_CTRL));
+ inputs.push_back(InputEventKey::create_reference(Key::HOME));
default_builtin_cache.insert("ui_text_caret_line_start.macos", inputs);
inputs = List<Ref<InputEvent>>();
@@ -645,6 +646,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::E | KeyModifierMask::CTRL));
inputs.push_back(InputEventKey::create_reference(Key::RIGHT | KeyModifierMask::CMD_OR_CTRL));
+ inputs.push_back(InputEventKey::create_reference(Key::END));
default_builtin_cache.insert("ui_text_caret_line_end.macos", inputs);
// Text Caret Movement Page Up/Down
@@ -665,6 +667,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::UP | KeyModifierMask::CMD_OR_CTRL));
+ inputs.push_back(InputEventKey::create_reference(Key::HOME | KeyModifierMask::CMD_OR_CTRL));
default_builtin_cache.insert("ui_text_caret_document_start.macos", inputs);
inputs = List<Ref<InputEvent>>();
@@ -673,6 +676,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::DOWN | KeyModifierMask::CMD_OR_CTRL));
+ inputs.push_back(InputEventKey::create_reference(Key::END | KeyModifierMask::CMD_OR_CTRL));
default_builtin_cache.insert("ui_text_caret_document_end.macos", inputs);
// Text Caret Addition Below/Above
diff --git a/core/io/logger.cpp b/core/io/logger.cpp
index 1476b8ccac..a24277fe72 100644
--- a/core/io/logger.cpp
+++ b/core/io/logger.cpp
@@ -212,7 +212,7 @@ void RotatedFileLogger::logv(const char *p_format, va_list p_list, bool p_err) {
// Strip ANSI escape codes (such as those inserted by `print_rich()`)
// before writing to file, as text editors cannot display those
// correctly.
- file->store_string(strip_ansi_regex->sub(String(buf), "", true));
+ file->store_string(strip_ansi_regex->sub(String::utf8(buf), "", true));
#else
file->store_buffer((uint8_t *)buf, len);
#endif // MODULE_REGEX_ENABLED
diff --git a/core/io/packet_peer_udp.cpp b/core/io/packet_peer_udp.cpp
index 32030146bb..fae3de2a98 100644
--- a/core/io/packet_peer_udp.cpp
+++ b/core/io/packet_peer_udp.cpp
@@ -106,7 +106,7 @@ Error PacketPeerUDP::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
}
uint32_t size = 0;
- uint8_t ipv6[16];
+ uint8_t ipv6[16] = {};
rb.read(ipv6, 16, true);
packet_ip.set_ipv6(ipv6);
rb.read((uint8_t *)&packet_port, 4, true);
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index 58ad61b621..20dd192da1 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -304,9 +304,10 @@ void ResourceLoader::_thread_load_function(void *p_userdata) {
thread_load_mutex.unlock();
// Thread-safe either if it's the current thread or a brand new one.
- bool mq_override_present = false;
+ thread_local bool mq_override_present = false;
CallQueue *own_mq_override = nullptr;
if (load_nesting == 0) {
+ mq_override_present = false;
load_paths_stack = memnew(Vector<String>);
if (!load_task.dependent_path.is_empty()) {
@@ -326,10 +327,6 @@ void ResourceLoader::_thread_load_function(void *p_userdata) {
}
// --
- if (!Thread::is_main_thread()) {
- set_current_thread_safe_for_nodes(true);
- }
-
Ref<Resource> res = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_task.error, load_task.use_sub_threads, &load_task.progress);
if (mq_override_present) {
MessageQueue::get_singleton()->flush();
@@ -691,6 +688,7 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
Error wtp_task_err = FAILED;
if (loader_is_wtp) {
// Loading thread is in the worker pool.
+ load_task.awaited = true;
thread_load_mutex.unlock();
wtp_task_err = WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id);
}
@@ -715,7 +713,6 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
} else {
DEV_ASSERT(wtp_task_err == OK);
thread_load_mutex.lock();
- load_task.awaited = true;
}
} else {
// Loading thread is main or user thread.
diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp
index f272407869..984bb1c9c1 100644
--- a/core/math/a_star_grid_2d.cpp
+++ b/core/math/a_star_grid_2d.cpp
@@ -122,6 +122,10 @@ AStarGrid2D::CellShape AStarGrid2D::get_cell_shape() const {
}
void AStarGrid2D::update() {
+ if (!dirty) {
+ return;
+ }
+
points.clear();
const int32_t end_x = region.get_end().x;
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index cca83b514d..ceeb04b8ea 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -76,6 +76,21 @@ class PlaceholderExtensionInstance {
StringName class_name;
HashMap<StringName, Variant> properties;
+ // Checks if a property is from a runtime class, and not a non-runtime base class.
+ bool is_runtime_property(const StringName &p_property_name) {
+ StringName current_class_name = class_name;
+
+ while (ClassDB::is_class_runtime(current_class_name)) {
+ if (ClassDB::has_property(current_class_name, p_property_name, true)) {
+ return true;
+ }
+
+ current_class_name = ClassDB::get_parent_class(current_class_name);
+ }
+
+ return false;
+ }
+
public:
PlaceholderExtensionInstance(const StringName &p_class_name) {
class_name = p_class_name;
@@ -83,27 +98,24 @@ public:
~PlaceholderExtensionInstance() {}
- void set(const StringName &p_name, const Variant &p_value) {
- bool is_default_valid = false;
- Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name, &is_default_valid);
-
- // If there's a default value, then we know it's a valid property.
- if (is_default_valid) {
+ void set(const StringName &p_name, const Variant &p_value, bool &r_valid) {
+ r_valid = is_runtime_property(p_name);
+ if (r_valid) {
properties[p_name] = p_value;
}
}
- Variant get(const StringName &p_name) {
+ Variant get(const StringName &p_name, bool &r_valid) {
const Variant *value = properties.getptr(p_name);
Variant ret;
if (value) {
ret = *value;
+ r_valid = true;
} else {
- bool is_default_valid = false;
- Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name, &is_default_valid);
- if (is_default_valid) {
- ret = default_value;
+ r_valid = is_runtime_property(p_name);
+ if (r_valid) {
+ ret = ClassDB::class_get_default_property_value(class_name, p_name);
}
}
@@ -115,10 +127,10 @@ public:
const StringName &name = *(StringName *)p_name;
const Variant &value = *(const Variant *)p_value;
- self->set(name, value);
+ bool valid = false;
+ self->set(name, value, valid);
- // We have to return true so Godot doesn't try to call the real setter function.
- return true;
+ return valid;
}
static GDExtensionBool placeholder_instance_get(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret) {
@@ -126,10 +138,10 @@ public:
const StringName &name = *(StringName *)p_name;
Variant *value = (Variant *)r_ret;
- *value = self->get(name);
+ bool valid = false;
+ *value = self->get(name, valid);
- // We have to return true so Godot doesn't try to call the real getter function.
- return true;
+ return valid;
}
static const GDExtensionPropertyInfo *placeholder_instance_get_property_list(GDExtensionClassInstancePtr p_instance, uint32_t *r_count) {
@@ -172,9 +184,9 @@ public:
static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata) {
ClassDB::ClassInfo *ti = (ClassDB::ClassInfo *)p_class_userdata;
- // Find the closest native parent.
+ // Find the closest native parent, that isn't a runtime class.
ClassDB::ClassInfo *native_parent = ti->inherits_ptr;
- while (native_parent->gdextension) {
+ while (native_parent->gdextension || native_parent->is_runtime) {
native_parent = native_parent->inherits_ptr;
}
ERR_FAIL_NULL_V(native_parent->creation_func, nullptr);
@@ -1967,6 +1979,14 @@ bool ClassDB::is_class_reloadable(const StringName &p_class) {
return ti->reloadable;
}
+bool ClassDB::is_class_runtime(const StringName &p_class) {
+ OBJTYPE_RLOCK;
+
+ ClassInfo *ti = classes.getptr(p_class);
+ ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'.");
+ return ti->is_runtime;
+}
+
void ClassDB::add_resource_base_extension(const StringName &p_extension, const StringName &p_class) {
if (resource_base_extensions.has(p_extension)) {
return;
@@ -2078,6 +2098,11 @@ void ClassDB::register_extension_class(ObjectGDExtension *p_extension) {
ClassInfo *parent = classes.getptr(p_extension->parent_class_name);
+#ifdef TOOLS_ENABLED
+ // @todo This is a limitation of the current implementation, but it should be possible to remove.
+ ERR_FAIL_COND_MSG(p_extension->is_runtime && parent->gdextension && !parent->is_runtime, "Extension runtime class " + String(p_extension->class_name) + " cannot descend from " + parent->name + " which isn't also a runtime class");
+#endif
+
ClassInfo c;
c.api = p_extension->editor_class ? API_EDITOR_EXTENSION : API_EXTENSION;
c.gdextension = p_extension;
diff --git a/core/object/class_db.h b/core/object/class_db.h
index caee2e8e10..228b82b588 100644
--- a/core/object/class_db.h
+++ b/core/object/class_db.h
@@ -461,6 +461,7 @@ public:
static bool is_class_exposed(const StringName &p_class);
static bool is_class_reloadable(const StringName &p_class);
+ static bool is_class_runtime(const StringName &p_class);
static void add_resource_base_extension(const StringName &p_extension, const StringName &p_class);
static void get_resource_base_extensions(List<String> *p_extensions);
diff --git a/core/variant/array.cpp b/core/variant/array.cpp
index 3685515db5..54cd1eda2f 100644
--- a/core/variant/array.cpp
+++ b/core/variant/array.cpp
@@ -235,7 +235,7 @@ void Array::assign(const Array &p_array) {
for (int i = 0; i < size; i++) {
const Variant &element = source[i];
if (element.get_type() != Variant::NIL && (element.get_type() != Variant::OBJECT || !typed.validate_object(element, "assign"))) {
- ERR_FAIL_MSG(vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(element.get_type()), Variant::get_type_name(typed.type)));
+ ERR_FAIL_MSG(vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(element.get_type()), Variant::get_type_name(typed.type)));
}
}
_p->array = p_array._p->array;
@@ -258,11 +258,11 @@ void Array::assign(const Array &p_array) {
continue;
}
if (!Variant::can_convert_strict(value->get_type(), typed.type)) {
- ERR_FAIL_MSG("Unable to convert array index " + itos(i) + " from '" + Variant::get_type_name(value->get_type()) + "' to '" + Variant::get_type_name(typed.type) + "'.");
+ ERR_FAIL_MSG(vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type)));
}
Callable::CallError ce;
Variant::construct(typed.type, data[i], &value, 1, ce);
- ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type)));
+ ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type)));
}
} else if (Variant::can_convert_strict(source_typed.type, typed.type)) {
// from primitives to different convertible primitives
@@ -270,7 +270,7 @@ void Array::assign(const Array &p_array) {
const Variant *value = source + i;
Callable::CallError ce;
Variant::construct(typed.type, data[i], &value, 1, ce);
- ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type)));
+ ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type)));
}
} else {
ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Array[%s]" to "Array[%s]".)", Variant::get_type_name(source_typed.type), Variant::get_type_name(typed.type)));
diff --git a/core/variant/variant.h b/core/variant/variant.h
index f352af24da..1cb3580c01 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -857,7 +857,7 @@ String vformat(const String &p_text, const VarArgs... p_args) {
bool error = false;
String fmt = p_text.sprintf(args_array, &error);
- ERR_FAIL_COND_V_MSG(error, String(), fmt);
+ ERR_FAIL_COND_V_MSG(error, String(), String("Formatting error in string \"") + p_text + "\": " + fmt + ".");
return fmt;
}
diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml
index 3b7a6e66fe..80fd52c684 100644
--- a/doc/classes/Animation.xml
+++ b/doc/classes/Animation.xml
@@ -560,7 +560,7 @@
<param index="0" name="track_idx" type="int" />
<param index="1" name="path" type="NodePath" />
<description>
- Sets the path of a track. Paths must be valid scene-tree paths to a node and must be specified starting from the parent node of the node that will reproduce the animation. Tracks that control properties or bones must append their name after the path, separated by [code]":"[/code].
+ Sets the path of a track. Paths must be valid scene-tree paths to a node and must be specified starting from the [member AnimationMixer.root_node] that will reproduce the animation. Tracks that control properties or bones must append their name after the path, separated by [code]":"[/code].
For example, [code]"character/skeleton:ankle"[/code] or [code]"character/mesh:transform/local"[/code].
</description>
</method>
diff --git a/doc/classes/AnimationMixer.xml b/doc/classes/AnimationMixer.xml
index 8a493ce91f..58ef118e46 100644
--- a/doc/classes/AnimationMixer.xml
+++ b/doc/classes/AnimationMixer.xml
@@ -304,8 +304,8 @@
This makes it more convenient to preview and edit animations in the editor, as changes to the scene will not be saved as long as they are set in the reset animation.
</member>
<member name="root_motion_track" type="NodePath" setter="set_root_motion_track" getter="get_root_motion_track" default="NodePath(&quot;&quot;)">
- The path to the Animation track used for root motion. Paths must be valid scene-tree paths to a node, and must be specified starting from the parent node of the node that will reproduce the animation. To specify a track that controls properties or bones, append its name after the path, separated by [code]":"[/code]. For example, [code]"character/skeleton:ankle"[/code] or [code]"character/mesh:transform/local"[/code].
- If the track has type [constant Animation.TYPE_POSITION_3D], [constant Animation.TYPE_ROTATION_3D] or [constant Animation.TYPE_SCALE_3D] the transformation will be canceled visually, and the animation will appear to stay in place. See also [method get_root_motion_position], [method get_root_motion_rotation], [method get_root_motion_scale] and [RootMotionView].
+ The path to the Animation track used for root motion. Paths must be valid scene-tree paths to a node, and must be specified starting from the parent node of the node that will reproduce the animation. The [member root_motion_track] uses the same format as [method Animation.track_set_path], but note that a bone must be specified.
+ If the track has type [constant Animation.TYPE_POSITION_3D], [constant Animation.TYPE_ROTATION_3D], or [constant Animation.TYPE_SCALE_3D] the transformation will be canceled visually, and the animation will appear to stay in place. See also [method get_root_motion_position], [method get_root_motion_rotation], [method get_root_motion_scale], and [RootMotionView].
</member>
<member name="root_node" type="NodePath" setter="set_root_node" getter="get_root_node" default="NodePath(&quot;..&quot;)">
The node which node path references will travel from.
diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml
index 326b71c588..3731b8dcf1 100644
--- a/doc/classes/Array.xml
+++ b/doc/classes/Array.xml
@@ -4,41 +4,31 @@
A built-in data structure that holds a sequence of elements.
</brief_description>
<description>
- An array data structure that can contain a sequence of elements of any type. Elements are accessed by a numerical index starting at 0. Negative indices are used to count from the back (-1 is the last element, -2 is the second to last, etc.).
+ An array data structure that can contain a sequence of elements of any [Variant] type. Elements are accessed by a numerical index starting at 0. Negative indices are used to count from the back (-1 is the last element, -2 is the second to last, etc.).
[b]Example:[/b]
[codeblocks]
[gdscript]
- var array = ["One", 2, 3, "Four"]
- print(array[0]) # One.
- print(array[2]) # 3.
- print(array[-1]) # Four.
- array[2] = "Three"
- print(array[-2]) # Three.
- [/gdscript]
- [csharp]
- var array = new Godot.Collections.Array{"One", 2, 3, "Four"};
- GD.Print(array[0]); // One.
- GD.Print(array[2]); // 3.
- GD.Print(array[array.Count - 1]); // Four.
- array[2] = "Three";
- GD.Print(array[array.Count - 2]); // Three.
- [/csharp]
- [/codeblocks]
- Arrays can be concatenated using the [code]+[/code] operator:
- [codeblocks]
- [gdscript]
- var array1 = ["One", 2]
- var array2 = [3, "Four"]
- print(array1 + array2) # ["One", 2, 3, "Four"]
+ var array = ["First", 2, 3, "Last"]
+ print(array[0]) # Prints "First"
+ print(array[2]) # Prints 3
+ print(array[-1]) # Prints "Last"
+
+ array[1] = "Second"
+ print(array[1]) # Prints "Second"
+ print(array[-3]) # Prints "Second"
[/gdscript]
[csharp]
- // Array concatenation is not possible with C# arrays, but is with Godot.Collections.Array.
- var array1 = new Godot.Collections.Array{"One", 2};
- var array2 = new Godot.Collections.Array{3, "Four"};
- GD.Print(array1 + array2); // Prints [One, 2, 3, Four]
+ var array = new Godot.Collections.Array{"First", 2, 3, "Last"};
+ GD.Print(array[0]); // Prints "First"
+ GD.Print(array[2]); // Prints 3
+ GD.Print(array[array.Count - 1]); // Prints "Last"
+
+ array[2] = "Second";
+ GD.Print(array[1]); // Prints "Second"
+ GD.Print(array[array.Count - 3]); // Prints "Second"
[/csharp]
[/codeblocks]
- [b]Note:[/b] Arrays are always passed by reference. To get a copy of an array that can be modified independently of the original array, use [method duplicate].
+ [b]Note:[/b] Arrays are always passed by [b]reference[/b]. To get a copy of an array that can be modified independently of the original array, use [method duplicate].
[b]Note:[/b] Erasing elements while iterating over arrays is [b]not[/b] supported and will result in unpredictable behavior.
[b]Differences between packed arrays, typed arrays, and untyped arrays:[/b] Packed arrays are generally faster to iterate on and modify compared to a typed array of the same type (e.g. [PackedInt64Array] versus [code]Array[int][/code]). Also, packed arrays consume less memory. As a downside, packed arrays are less flexible as they don't offer as many convenience methods such as [method Array.map]. Typed arrays are in turn faster to iterate on and modify than untyped arrays.
</description>
@@ -58,29 +48,32 @@
<param index="2" name="class_name" type="StringName" />
<param index="3" name="script" type="Variant" />
<description>
- Creates a typed array from the [param base] array. All arguments are required.
- - [param type] is the built-in type as a [enum Variant.Type] constant, for example [constant TYPE_INT].
- - [param class_name] is the [b]native[/b] class name, for example [Node]. If [param type] is not [constant TYPE_OBJECT], must be an empty string.
- - [param script] is the associated script. Must be a [Script] instance or [code]null[/code].
- Examples:
+ Creates a typed array from the [param base] array. A typed array can only contain elements of the given type, or that inherit from the given class, as described by this constructor's parameters:
+ - [param type] is the built-in [Variant] type, as one the [enum Variant.Type] constants.
+ - [param class_name] is the built-in class name (see [method Object.get_class]).
+ - [param script] is the associated script. It must be a [Script] instance or [code]null[/code].
+ If [param type] is not [constant TYPE_OBJECT], [param class_name] must be an empty [StringName] and [param script] must be [code]null[/code].
[codeblock]
- class_name MyNode
+ class_name Sword
extends Node
- class MyClass:
+ class Stats:
pass
func _ready():
- var a = Array([], TYPE_INT, &amp;"", null) # Array[int]
- var b = Array([], TYPE_OBJECT, &amp;"Node", null) # Array[Node]
- var c = Array([], TYPE_OBJECT, &amp;"Node", MyNode) # Array[MyNode]
- var d = Array([], TYPE_OBJECT, &amp;"RefCounted", MyClass) # Array[MyClass]
+ var a = Array([], TYPE_INT, "", null) # Array[int]
+ var b = Array([], TYPE_OBJECT, "Node", null) # Array[Node]
+ var c = Array([], TYPE_OBJECT, "Node", Sword) # Array[Sword]
+ var d = Array([], TYPE_OBJECT, "RefCounted", Stats) # Array[Stats]
[/codeblock]
- [b]Note:[/b] This constructor can be useful if you want to create a typed array on the fly, but you are not required to use it. In GDScript you can use a temporary variable with the static type you need and then pass it:
+ The [param base] array's elements are converted when necessary. If this is not possible or [param base] is already typed, this constructor fails and returns an empty [Array].
+ In GDScript, this constructor is usually not necessary, as it is possible to create a typed array through static typing:
[codeblock]
- func _ready():
- var a: Array[int] = []
- some_func(a)
+ var numbers: Array[float] = []
+ var children: Array[Node] = [$Node, $Sprite2D, $RigidBody3D]
+
+ var integers: Array[int] = [0.2, 4.5, -2.0]
+ print(integers) # Prints [0, 4, -2]
[/codeblock]
</description>
</constructor>
@@ -167,20 +160,44 @@
<return type="bool" />
<param index="0" name="method" type="Callable" />
<description>
- Calls the provided [Callable] on each element in the array and returns [code]true[/code] if the [Callable] returns [code]true[/code] for [i]all[/i] elements in the array. If the [Callable] returns [code]false[/code] for one array element or more, this method returns [code]false[/code].
- The callable's method should take one [Variant] parameter (the current array element) and return a boolean value.
- [codeblock]
+ Calls the given [Callable] on each element in the array and returns [code]true[/code] if the [Callable] returns [code]true[/code] for [i]all[/i] elements in the array. If the [Callable] returns [code]false[/code] for one array element or more, this method returns [code]false[/code].
+ The [param method] should take one [Variant] parameter (the current array element) and return a [bool].
+ [codeblocks]
+ [gdscript]
+ func greater_than_5(number):
+ return number &gt; 5
+
func _ready():
- print([6, 10, 6].all(greater_than_5)) # Prints True (3/3 elements evaluate to `true`).
- print([4, 10, 4].all(greater_than_5)) # Prints False (1/3 elements evaluate to `true`).
- print([4, 4, 4].all(greater_than_5)) # Prints False (0/3 elements evaluate to `true`).
- print([].all(greater_than_5)) # Prints True (0/0 elements evaluate to `true`).
+ print([6, 10, 6].all(greater_than_5)) # Prints true (3/3 elements evaluate to true).
+ print([4, 10, 4].all(greater_than_5)) # Prints false (1/3 elements evaluate to true).
+ print([4, 4, 4].all(greater_than_5)) # Prints false (0/3 elements evaluate to true).
+ print([].all(greater_than_5)) # Prints true (0/0 elements evaluate to true).
+
+ # Same as the first line above, but using a lambda function.
+ print([6, 10, 6].all(func(element): return element &gt; 5)) # Prints true
+ [/gdscript]
+ [csharp]
+ private static bool GreaterThan5(int number)
+ {
+ return number &gt; 5;
+ }
- print([6, 10, 6].all(func(number): return number &gt; 5)) # Prints True. Same as the first line above, but using lambda function.
+ public override void _Ready()
+ {
+ // Prints true (3/3 elements evaluate to true).
+ GD.Print(new Godot.Collections.Array&gt;int&lt; { 6, 10, 6 }.All(GreaterThan5));
+ // Prints false (1/3 elements evaluate to true).
+ GD.Print(new Godot.Collections.Array&gt;int&lt; { 4, 10, 4 }.All(GreaterThan5));
+ // Prints false (0/3 elements evaluate to true).
+ GD.Print(new Godot.Collections.Array&gt;int&lt; { 4, 4, 4 }.All(GreaterThan5));
+ // Prints true (0/0 elements evaluate to true).
+ GD.Print(new Godot.Collections.Array&gt;int&lt; { }.All(GreaterThan5));
- func greater_than_5(number):
- return number &gt; 5
- [/codeblock]
+ // Same as the first line above, but using a lambda function.
+ GD.Print(new Godot.Collections.Array&gt;int&lt; { 6, 10, 6 }.All(element =&gt; element &gt; 5)); // Prints true
+ }
+ [/csharp]
+ [/codeblocks]
See also [method any], [method filter], [method map] and [method reduce].
[b]Note:[/b] Unlike relying on the size of an array returned by [method filter], this method will return as early as possible to improve performance (especially with large arrays).
[b]Note:[/b] For an empty array, this method [url=https://en.wikipedia.org/wiki/Vacuous_truth]always[/url] returns [code]true[/code].
@@ -190,19 +207,20 @@
<return type="bool" />
<param index="0" name="method" type="Callable" />
<description>
- Calls the provided [Callable] on each element in the array and returns [code]true[/code] if the [Callable] returns [code]true[/code] for [i]one or more[/i] elements in the array. If the [Callable] returns [code]false[/code] for all elements in the array, this method returns [code]false[/code].
- The callable's method should take one [Variant] parameter (the current array element) and return a boolean value.
+ Calls the given [Callable] on each element in the array and returns [code]true[/code] if the [Callable] returns [code]true[/code] for [i]one or more[/i] elements in the array. If the [Callable] returns [code]false[/code] for all elements in the array, this method returns [code]false[/code].
+ The [param method] should take one [Variant] parameter (the current array element) and return a [bool].
[codeblock]
- func _ready():
- print([6, 10, 6].any(greater_than_5)) # Prints True (3 elements evaluate to `true`).
- print([4, 10, 4].any(greater_than_5)) # Prints True (1 elements evaluate to `true`).
- print([4, 4, 4].any(greater_than_5)) # Prints False (0 elements evaluate to `true`).
- print([].any(greater_than_5)) # Prints False (0 elements evaluate to `true`).
-
- print([6, 10, 6].any(func(number): return number &gt; 5)) # Prints True. Same as the first line above, but using lambda function.
-
func greater_than_5(number):
return number &gt; 5
+
+ func _ready():
+ print([6, 10, 6].any(greater_than_5)) # Prints true (3 elements evaluate to true).
+ print([4, 10, 4].any(greater_than_5)) # Prints true (1 elements evaluate to true).
+ print([4, 4, 4].any(greater_than_5)) # Prints false (0 elements evaluate to true).
+ print([].any(greater_than_5)) # Prints false (0 elements evaluate to true).
+
+ # Same as the first line above, but using a lambda function.
+ print([6, 10, 6].any(func(number): return number &gt; 5)) # Prints true
[/codeblock]
See also [method all], [method filter], [method map] and [method reduce].
[b]Note:[/b] Unlike relying on the size of an array returned by [method filter], this method will return as early as possible to improve performance (especially with large arrays).
@@ -213,19 +231,19 @@
<return type="void" />
<param index="0" name="value" type="Variant" />
<description>
- Appends an element at the end of the array (alias of [method push_back]).
+ Appends [param value] at the end of the array (alias of [method push_back]).
</description>
</method>
<method name="append_array">
<return type="void" />
<param index="0" name="array" type="Array" />
<description>
- Appends another array at the end of this array.
+ Appends another [param array] at the end of this array.
[codeblock]
- var array1 = [1, 2, 3]
- var array2 = [4, 5, 6]
- array1.append_array(array2)
- print(array1) # Prints [1, 2, 3, 4, 5, 6].
+ var numbers = [1, 2, 3]
+ var extra = [4, 5, 6]
+ numbers.append_array(extra)
+ print(nums) # Prints [1, 2, 3, 4, 5, 6]
[/codeblock]
</description>
</method>
@@ -239,8 +257,8 @@
<method name="back" qualifiers="const">
<return type="Variant" />
<description>
- Returns the last element of the array. Prints an error and returns [code]null[/code] if the array is empty.
- [b]Note:[/b] Calling this function is not the same as writing [code]array[-1][/code]. If the array is empty, accessing by index will pause project execution when running from the editor.
+ Returns the last element of the array. If the array is empty, fails and returns [code]null[/code]. See also [method front].
+ [b]Note:[/b] Unlike with the [code][][/code] operator ([code]array[-1][/code]), an error is generated without stopping project execution.
</description>
</method>
<method name="bsearch" qualifiers="const">
@@ -248,13 +266,20 @@
<param index="0" name="value" type="Variant" />
<param index="1" name="before" type="bool" default="true" />
<description>
- Finds the index of an existing value (or the insertion index that maintains sorting order, if the value is not yet present in the array) using binary search. Optionally, a [param before] specifier can be passed. If [code]false[/code], the returned index comes after all existing entries of the value in the array.
+ Returns the index of [param value] in the sorted array. If it cannot be found, returns where [param value] should be inserted to keep the array sorted. The algorithm used is [url=https://en.wikipedia.org/wiki/Binary_search_algorithm]binary search[/url].
+ If [param before] is [code]true[/code] (as by default), the returned index comes before all existing elements equal to [param value] in the array.
[codeblock]
- var array = ["a", "b", "c", "c", "d", "e"]
- print(array.bsearch("c", true)) # Prints 2, at the first matching element.
- print(array.bsearch("c", false)) # Prints 4, after the last matching element, pointing to "d".
+ var numbers = [2, 4, 8, 10]
+ var idx = numbers.bsearch(7)
+
+ numbers.insert(idx, 7)
+ print(numbers) # Prints [2, 4, 7, 8, 10]
+
+ var fruits = ["Apple", "Lemon", "Lemon", "Orange"]
+ print(fruits.bsearch("Lemon", true)) # Prints 1, points at the first "Lemon".
+ print(fruits.bsearch("Lemon", false)) # Prints 3, points at "Orange".
[/codeblock]
- [b]Note:[/b] Calling [method bsearch] on an unsorted array results in unexpected behavior.
+ [b]Note:[/b] Calling [method bsearch] on an [i]unsorted[/i] array will result in unexpected behavior. Use [method sort] before calling this method.
</description>
</method>
<method name="bsearch_custom" qualifiers="const">
@@ -263,15 +288,36 @@
<param index="1" name="func" type="Callable" />
<param index="2" name="before" type="bool" default="true" />
<description>
- Finds the index of an existing value (or the insertion index that maintains sorting order, if the value is not yet present in the array) using binary search and a custom comparison method. Optionally, a [param before] specifier can be passed. If [code]false[/code], the returned index comes after all existing entries of the value in the array. The custom method receives two arguments (an element from the array and the value searched for) and must return [code]true[/code] if the first argument is less than the second, and return [code]false[/code] otherwise.
- [b]Note:[/b] The custom method must accept the two arguments in any order, you cannot rely on that the first argument will always be from the array.
- [b]Note:[/b] Calling [method bsearch_custom] on an unsorted array results in unexpected behavior.
+ Returns the index of [param value] in the sorted array. If it cannot be found, returns where [param value] should be inserted to keep the array sorted (using [param func] for the comparisons). The algorithm used is [url=https://en.wikipedia.org/wiki/Binary_search_algorithm]binary search[/url].
+ Similar to [method sort_custom], [param func] is called as many times as necessary, receiving one array element and [param value] as arguments. The function should return [code]true[/code] if the array element should be [i]behind[/i] [param value], otherwise it should return [code]false[/code].
+ If [param before] is [code]true[/code] (as by default), the returned index comes before all existing elements equal to [param value] in the array.
+ [codeblock]
+ func sort_by_amount(a, b):
+ if a[1] &lt; b[1]:
+ return true
+ return false
+
+ func _ready():
+ var my_items = [["Tomato", 2], ["Kiwi", 5], ["Rice", 9]]
+
+ var apple = ["Apple", 5]
+ # "Apple" is inserted before "Kiwi".
+ my_items.insert(my_items.bsearch_custom(apple, sort_by_amount, true), apple)
+
+ var banana = ["Banana", 5]
+ # "Banana" is inserted after "Kiwi".
+ my_items.insert(my_items.bsearch_custom(banana, sort_by_amount, false), banana)
+
+ # Prints [["Tomato", 2], ["Apple", 5], ["Kiwi", 5], ["Banana", 5], ["Rice", 9]]
+ print(my_items)
+ [/codeblock]
+ [b]Note:[/b] Calling [method bsearch_custom] on an [i]unsorted[/i] array will result in unexpected behavior. Use [method sort_custom] with [param func] before calling this method.
</description>
</method>
<method name="clear">
<return type="void" />
<description>
- Clears the array. This is equivalent to using [method resize] with a size of [code]0[/code].
+ Removes all elements from the array. This is equivalent to using [method resize] with a size of [code]0[/code].
</description>
</method>
<method name="count" qualifiers="const">
@@ -285,53 +331,57 @@
<return type="Array" />
<param index="0" name="deep" type="bool" default="false" />
<description>
- Returns a copy of the array.
- If [param deep] is [code]true[/code], a deep copy is performed: all nested arrays and dictionaries are duplicated and will not be shared with the original array. If [code]false[/code], a shallow copy is made and references to the original nested arrays and dictionaries are kept, so that modifying a sub-array or dictionary in the copy will also impact those referenced in the source array. Note that any [Object]-derived elements will be shallow copied regardless of the [param deep] setting.
+ Returns a new copy of the array.
+ By default, a [b]shallow[/b] copy is returned: all nested [Array] and [Dictionary] elements are shared with the original array. Modifying them in one array will also affect them in the other.[br]If [param deep] is [code]true[/code], a [b]deep[/b] copy is returned: all nested arrays and dictionaries are also duplicated (recursively).
</description>
</method>
<method name="erase">
<return type="void" />
<param index="0" name="value" type="Variant" />
<description>
- Removes the first occurrence of a value from the array. If the value does not exist in the array, nothing happens. To remove an element by index, use [method remove_at] instead.
- [b]Note:[/b] This method acts in-place and doesn't return a modified array.
- [b]Note:[/b] On large arrays, this method will be slower if the removed element is close to the beginning of the array (index 0). This is because all elements placed after the removed element have to be reindexed.
- [b]Note:[/b] Do not erase entries while iterating over the array.
+ Finds and removes the first occurrence of [param value] from the array. If [param value] does not exist in the array, nothing happens. To remove an element by index, use [method remove_at] instead.
+ [b]Note:[/b] This method shifts every element's index after the removed [param value] back, which may have a noticeable performance cost, especially on larger arrays.
+ [b]Note:[/b] Erasing elements while iterating over arrays is [b]not[/b] supported and will result in unpredictable behavior.
</description>
</method>
<method name="fill">
<return type="void" />
<param index="0" name="value" type="Variant" />
<description>
- Assigns the given value to all elements in the array. This can typically be used together with [method resize] to create an array with a given size and initialized elements:
+ Assigns the given [param value] to all elements in the array.
+ This method can often be combined with [method resize] to create an array with a given size and initialized elements:
[codeblocks]
[gdscript]
var array = []
- array.resize(10)
- array.fill(0) # Initialize the 10 elements to 0.
+ array.resize(5)
+ array.fill(2)
+ print(array) # Prints [2, 2, 2, 2, 2]
[/gdscript]
[csharp]
var array = new Godot.Collections.Array();
- array.Resize(10);
- array.Fill(0); // Initialize the 10 elements to 0.
+ array.Resize(5);
+ array.Fill(2);
+ GD.Print(array); // Prints [2, 2, 2, 2, 2]
[/csharp]
[/codeblocks]
- [b]Note:[/b] If [param value] is of a reference type ([Object]-derived, [Array], [Dictionary], etc.) then the array is filled with the references to the same object, i.e. no duplicates are created.
+ [b]Note:[/b] If [param value] is a [Variant] passed by reference ([Object]-derived, [Array], [Dictionary], etc.), the array will be filled with references to the same [param value], which are not duplicates.
</description>
</method>
<method name="filter" qualifiers="const">
<return type="Array" />
<param index="0" name="method" type="Callable" />
<description>
- Calls the provided [Callable] on each element in the array and returns a new array with the elements for which the method returned [code]true[/code].
- The callable's method should take one [Variant] parameter (the current array element) and return a boolean value.
+ Calls the given [Callable] on each element in the array and returns a new, filtered [Array].
+ The [param method] receives one of the array elements as an argument, and should return [code]true[/code] to add the element to the filtered array, or [code]false[/code] to exclude it.
[codeblock]
+ func is_even(number):
+ return number % 2 == 0
+
func _ready():
- print([1, 2, 3].filter(remove_1)) # Prints [2, 3].
- print([1, 2, 3].filter(func(number): return number != 1)) # Same as above, but using lambda function.
+ print([1, 4, 5, 8].filter(is_even)) # Prints [4, 8]
- func remove_1(number):
- return number != 1
+ # Same as above, but using a lambda function.
+ print([1, 4, 5, 8].filter(func(number): return number % 2 == 0))
[/codeblock]
See also [method any], [method all], [method map] and [method reduce].
</description>
@@ -341,78 +391,70 @@
<param index="0" name="what" type="Variant" />
<param index="1" name="from" type="int" default="0" />
<description>
- Searches the array for a value and returns its index or [code]-1[/code] if not found. Optionally, the initial search index can be passed.
+ Returns the index of the [b]first[/b] occurrence of [param what] in this array, or [code]-1[/code] if there are none. The search's start can be specified with [param from], continuing to the end of the array.
+ [b]Note:[/b] If you just want to know whether the array contains [param what], use [method has] ([code]Contains[/code] in C#). In GDScript, you may also use the [code]in[/code] operator.
+ [b]Note:[/b] For performance reasons, the search is affected by [param what]'s [enum Variant.Type]. For example, [code]7[/code] ([int]) and [code]7.0[/code] ([float]) are not considered equal for this method.
</description>
</method>
<method name="front" qualifiers="const">
<return type="Variant" />
<description>
- Returns the first element of the array. Prints an error and returns [code]null[/code] if the array is empty.
- [b]Note:[/b] Calling this function is not the same as writing [code]array[0][/code]. If the array is empty, accessing by index will pause project execution when running from the editor.
+ Returns the first element of the array. If the array is empty, fails and returns [code]null[/code]. See also [method back].
+ [b]Note:[/b] Unlike with the [code][][/code] operator ([code]array[0][/code]), an error is generated without stopping project execution.
</description>
</method>
<method name="get_typed_builtin" qualifiers="const">
<return type="int" />
<description>
- Returns the built-in type of the typed array as a [enum Variant.Type] constant. If the array is not typed, returns [constant TYPE_NIL].
+ Returns the built-in [Variant] type of the typed array as a [enum Variant.Type] constant. If the array is not typed, returns [constant TYPE_NIL]. See also [method is_typed].
</description>
</method>
<method name="get_typed_class_name" qualifiers="const">
<return type="StringName" />
<description>
- Returns the [b]native[/b] class name of the typed array if the built-in type is [constant TYPE_OBJECT]. Otherwise, this method returns an empty string.
+ Returns the [b]built-in[/b] class name of the typed array, if the built-in [Variant] type [constant TYPE_OBJECT]. Otherwise, returns an empty [StringName]. See also [method is_typed] and [method Object.get_class].
</description>
</method>
<method name="get_typed_script" qualifiers="const">
<return type="Variant" />
<description>
- Returns the script associated with the typed array. This method returns a [Script] instance or [code]null[/code].
+ Returns the [Script] instance associated with this typed array, or [code]null[/code] if it does not exist. See also [method is_typed].
</description>
</method>
<method name="has" qualifiers="const" keywords="includes, contains">
<return type="bool" />
<param index="0" name="value" type="Variant" />
<description>
- Returns [code]true[/code] if the array contains the given value.
+ Returns [code]true[/code] if the array contains the given [param value].
[codeblocks]
[gdscript]
- print(["inside", 7].has("inside")) # True
- print(["inside", 7].has("outside")) # False
- print(["inside", 7].has(7)) # True
- print(["inside", 7].has("7")) # False
+ print(["inside", 7].has("inside")) # Prints true
+ print(["inside", 7].has("outside")) # Prints false
+ print(["inside", 7].has(7)) # Prints true
+ print(["inside", 7].has("7")) # Prints false
[/gdscript]
[csharp]
var arr = new Godot.Collections.Array { "inside", 7 };
- // has is renamed to Contains
- GD.Print(arr.Contains("inside")); // True
- GD.Print(arr.Contains("outside")); // False
- GD.Print(arr.Contains(7)); // True
- GD.Print(arr.Contains("7")); // False
- [/csharp]
- [/codeblocks]
- [b]Note:[/b] This is equivalent to using the [code]in[/code] operator as follows:
- [codeblocks]
- [gdscript]
- # Will evaluate to `true`.
- if 2 in [2, 4, 6, 8]:
- print("Contains!")
- [/gdscript]
- [csharp]
- // As there is no "in" keyword in C#, you have to use Contains
- var array = new Godot.Collections.Array { 2, 4, 6, 8 };
- if (array.Contains(2))
- {
- GD.Print("Contains!");
- }
+ // By C# convention, this method is renamed to `Contains`.
+ GD.Print(arr.Contains("inside")); // Prints true
+ GD.Print(arr.Contains("outside")); // Prints false
+ GD.Print(arr.Contains(7)); // Prints true
+ GD.Print(arr.Contains("7")); // Prints false
[/csharp]
[/codeblocks]
+ In GDScript, this is equivalent to the [code]in[/code] operator:
+ [codeblock]
+ if 4 in [2, 4, 6, 8]:
+ print("4 is here!") # Will be printed.
+ [/codeblock]
+ [b]Note:[/b] For performance reasons, the search is affected by the [param value]'s [enum Variant.Type]. For example, [code]7[/code] ([int]) and [code]7.0[/code] ([float]) are not considered equal for this method.
</description>
</method>
<method name="hash" qualifiers="const">
<return type="int" />
<description>
Returns a hashed 32-bit integer value representing the array and its contents.
- [b]Note:[/b] [Array]s with equal content will always produce identical hash values. However, the reverse is not true. Returning identical hash values does [i]not[/i] imply the arrays are equal, because different arrays can have identical hash values due to hash collisions.
+ [b]Note:[/b] Arrays with equal hash values are [i]not[/i] guaranteed to be the same, as a result of hash collisions. On the countrary, arrays with different hash values are guaranteed to be different.
</description>
</method>
<method name="insert">
@@ -420,55 +462,64 @@
<param index="0" name="position" type="int" />
<param index="1" name="value" type="Variant" />
<description>
- Inserts a new element at a given position in the array. The position must be valid, or at the end of the array ([code]pos == size()[/code]). Returns [constant OK] on success, or one of the other [enum Error] values if the operation failed.
- [b]Note:[/b] This method acts in-place and doesn't return a modified array.
- [b]Note:[/b] On large arrays, this method will be slower if the inserted element is close to the beginning of the array (index 0). This is because all elements placed after the newly inserted element have to be reindexed.
+ Inserts a new element ([param value]) at a given index ([param position]) in the array. [param position] should be between [code]0[/code] and the array's [method size].
+ Returns [constant OK] on success, or one of the other [enum Error] constants if this method fails.
+ [b]Note:[/b] Every element's index after [param position] needs to be shifted forward, which may have a noticeable performance cost, especially on larger arrays.
</description>
</method>
<method name="is_empty" qualifiers="const">
<return type="bool" />
<description>
- Returns [code]true[/code] if the array is empty.
+ Returns [code]true[/code] if the array is empty ([code][][/code]). See also [method size].
</description>
</method>
<method name="is_read_only" qualifiers="const">
<return type="bool" />
<description>
- Returns [code]true[/code] if the array is read-only. See [method make_read_only]. Arrays are automatically read-only if declared with [code]const[/code] keyword.
+ Returns [code]true[/code] if the array is read-only. See [method make_read_only].
+ In GDScript, arrays are automatically read-only if declared with the [code]const[/code] keyword.
</description>
</method>
<method name="is_same_typed" qualifiers="const">
<return type="bool" />
<param index="0" name="array" type="Array" />
<description>
- Returns [code]true[/code] if the array is typed the same as [param array].
+ Returns [code]true[/code] if this array is typed the same as the given [param array]. See also [method is_typed].
</description>
</method>
<method name="is_typed" qualifiers="const">
<return type="bool" />
<description>
- Returns [code]true[/code] if the array is typed. Typed arrays can only store elements of their associated type and provide type safety for the [code][][/code] operator. Methods of typed array still return [Variant].
+ Returns [code]true[/code] if the array is typed. Typed arrays can only contain elements of a specific type, as defined by the typed array constructor. The methods of a typed array are still expected to return a generic [Variant].
+ In GDScript, it is possible to define a typed array with static typing:
+ [codeblock]
+ var numbers: Array[float] = [0.2, 4.2, -2.0]
+ print(numbers.is_typed()) # Prints true
+ [/codeblock]
</description>
</method>
<method name="make_read_only">
<return type="void" />
<description>
- Makes the array read-only, i.e. disabled modifying of the array's elements. Does not apply to nested content, e.g. content of nested arrays.
+ Makes the array read-only. The array's elements cannot be overridden with different values, and their order cannot change. Does not apply to nested elements, such as dictionaries.
+ In GDScript, arrays are automatically read-only if declared with the [code]const[/code] keyword.
</description>
</method>
<method name="map" qualifiers="const">
<return type="Array" />
<param index="0" name="method" type="Callable" />
<description>
- Calls the provided [Callable] for each element in the array and returns a new array filled with values returned by the method.
- The callable's method should take one [Variant] parameter (the current array element) and can return any [Variant].
+ Calls the given [Callable] for each element in the array and returns a new array filled with values returned by the [param method].
+ The [param method] should take one [Variant] parameter (the current array element) and can return any [Variant].
[codeblock]
+ func double(number):
+ return number * 2
+
func _ready():
- print([1, 2, 3].map(negate)) # Prints [-1, -2, -3].
- print([1, 2, 3].map(func(number): return -number)) # Same as above, but using lambda function.
+ print([1, 2, 3].map(double)) # Prints [2, 4, 6]
- func negate(number):
- return -number
+ # Same as above, but using a lambda function.
+ print([1, 2, 3].map(func(element): return element * 2))
[/codeblock]
See also [method filter], [method reduce], [method any] and [method all].
</description>
@@ -476,61 +527,52 @@
<method name="max" qualifiers="const">
<return type="Variant" />
<description>
- Returns the maximum value contained in the array if all elements are of comparable types. If the elements can't be compared, [code]null[/code] is returned.
- To find the maximum value using a custom comparator, you can use [method reduce]. In this example every array element is checked and the first maximum value is returned:
- [codeblock]
- func _ready():
- var arr = [Vector2(0, 1), Vector2(2, 0), Vector2(1, 1), Vector2(1, 0), Vector2(0, 2)]
- # In this example we compare the lengths.
- print(arr.reduce(func(max, val): return val if is_length_greater(val, max) else max))
-
- func is_length_greater(a, b):
- return a.length() &gt; b.length()
- [/codeblock]
+ Returns the maximum value contained in the array, if all elements can be compared. Otherwise, returns [code]null[/code]. See also [method min].
+ To find the maximum value using a custom comparator, you can use [method reduce].
</description>
</method>
<method name="min" qualifiers="const">
<return type="Variant" />
<description>
- Returns the minimum value contained in the array if all elements are of comparable types. If the elements can't be compared, [code]null[/code] is returned.
- See also [method max] for an example of using a custom comparator.
+ Returns the minimum value contained in the array, if all elements can be compared. Otherwise, returns [code]null[/code]. See also [method max].
</description>
</method>
<method name="pick_random" qualifiers="const">
<return type="Variant" />
<description>
- Returns a random value from the target array. Prints an error and returns [code]null[/code] if the array is empty.
+ Returns a random element from the array. Generates an error and returns [code]null[/code] if the array is empty.
[codeblocks]
[gdscript]
- var array: Array[int] = [1, 2, 3, 4]
- print(array.pick_random()) # Prints either of the four numbers.
+ # May print 1, 2, 3.25, or "Hi".
+ print([1, 2, 3.25, "Hi"].pick_random())
[/gdscript]
[csharp]
- var array = new Godot.Collections.Array { 1, 2, 3, 4 };
- GD.Print(array.PickRandom()); // Prints either of the four numbers.
+ var array = new Godot.Collections.Array { 1, 2, 3.25f, "Hi" };
+ GD.Print(array.PickRandom()); // May print 1, 2, 3.25, or "Hi".
[/csharp]
[/codeblocks]
+ [b]Note:[/b] Like many similar functions in the engine (such as [method @GlobalScope.randi] or [method shuffle]), this method uses a common, global random seed. To get a predictable outcome from this method, see [method @GlobalScope.seed].
</description>
</method>
<method name="pop_at">
<return type="Variant" />
<param index="0" name="position" type="int" />
<description>
- Removes and returns the element of the array at index [param position]. If negative, [param position] is considered relative to the end of the array. Leaves the array unchanged and returns [code]null[/code] if the array is empty or if it's accessed out of bounds. An error message is printed when the array is accessed out of bounds, but not when the array is empty.
- [b]Note:[/b] On large arrays, this method can be slower than [method pop_back] as it will reindex the array's elements that are located after the removed element. The larger the array and the lower the index of the removed element, the slower [method pop_at] will be.
+ Removes and returns the element of the array at index [param position]. If negative, [param position] is considered relative to the end of the array. Returns [code]null[/code] if the array is empty. If [param position] is out of bounds, an error message is also generated.
+ [b]Note:[/b] This method shifts every element's index after [param position] back, which may have a noticeable performance cost, especially on larger arrays.
</description>
</method>
<method name="pop_back">
<return type="Variant" />
<description>
- Removes and returns the last element of the array. Returns [code]null[/code] if the array is empty, without printing an error message. See also [method pop_front].
+ Removes and returns the last element of the array. Returns [code]null[/code] if the array is empty, without generating an error. See also [method pop_front].
</description>
</method>
<method name="pop_front">
<return type="Variant" />
<description>
- Removes and returns the first element of the array. Returns [code]null[/code] if the array is empty, without printing an error message. See also [method pop_back].
- [b]Note:[/b] On large arrays, this method is much slower than [method pop_back] as it will reindex all the array's elements every time it's called. The larger the array, the slower [method pop_front] will be.
+ Removes and returns the first element of the array. Returns [code]null[/code] if the array is empty, without generating an error. See also [method pop_back].
+ [b]Note:[/b] This method shifts every other element's index back, which may have a noticeable performance cost, especially on larger arrays.
</description>
</method>
<method name="push_back">
@@ -545,7 +587,7 @@
<param index="0" name="value" type="Variant" />
<description>
Adds an element at the beginning of the array. See also [method push_back].
- [b]Note:[/b] On large arrays, this method is much slower than [method push_back] as it will reindex all the array's elements every time it's called. The larger the array, the slower [method push_front] will be.
+ [b]Note:[/b] This method shifts every other element's index forward, which may have a noticeable performance cost, especially on larger arrays.
</description>
</method>
<method name="reduce" qualifiers="const">
@@ -553,15 +595,29 @@
<param index="0" name="method" type="Callable" />
<param index="1" name="accum" type="Variant" default="null" />
<description>
- Calls the provided [Callable] for each element in array and accumulates the result in [param accum].
- The callable's method takes two arguments: the current value of [param accum] and the current array element. If [param accum] is [code]null[/code] (default value), the iteration will start from the second element, with the first one used as initial value of [param accum].
+ Calls the given [Callable] for each element in array, accumulates the result in [param accum], then returns it.
+ The [param method] takes two arguments: the current value of [param accum] and the current array element. If [param accum] is [code]null[/code] (as by default), the iteration will start from the second element, with the first one used as initial value of [param accum].
[codeblock]
- func _ready():
- print([1, 2, 3].reduce(sum, 10)) # Prints 16.
- print([1, 2, 3].reduce(func(accum, number): return accum + number, 10)) # Same as above, but using lambda function.
-
func sum(accum, number):
return accum + number
+
+ func _ready():
+ print([1, 2, 3].reduce(sum, 0)) # Prints 6
+ print([1, 2, 3].reduce(sum, 10)) # Prints 16
+
+ # Same as above, but using a lambda function.
+ print([1, 2, 3].reduce(func(accum, number): return accum + number, 10))
+ [/codeblock]
+ If [method max] is not desirable, this method may also be used to implement a custom comparator:
+ [codeblock]
+ func _ready():
+ var arr = [Vector2(5, 0), Vector2(3, 4), Vector2(1, 2)]
+
+ var longest_vec = arr.reduce(func(max, vec): return vec if is_length_greater(vec, max) else max)
+ print(longest_vec) # Prints Vector2(3, 4).
+
+ func is_length_greater(a, b):
+ return a.length() &gt; b.length()
[/codeblock]
See also [method map], [method filter], [method any] and [method all].
</description>
@@ -570,25 +626,25 @@
<return type="void" />
<param index="0" name="position" type="int" />
<description>
- Removes an element from the array by index. If the index does not exist in the array, nothing happens. To remove an element by searching for its value, use [method erase] instead.
- [b]Note:[/b] This method acts in-place and doesn't return a modified array.
- [b]Note:[/b] On large arrays, this method will be slower if the removed element is close to the beginning of the array (index 0). This is because all elements placed after the removed element have to be reindexed.
- [b]Note:[/b] [param position] cannot be negative. To remove an element relative to the end of the array, use [code]arr.remove_at(arr.size() - (i + 1))[/code]. To remove the last element from the array without returning the value, use [code]arr.resize(arr.size() - 1)[/code].
+ Removes the element from the array at the given index ([param position]). If the index is out of bounds, this method fails.
+ If you need to return the removed element, use [method pop_at]. To remove an element by value, use [method erase] instead.
+ [b]Note:[/b] This method shifts every element's index after [param position] back, which may have a noticeable performance cost, especially on larger arrays.
+ [b]Note:[/b] The [param position] cannot be negative. To remove an element relative to the end of the array, use [code]arr.remove_at(arr.size() - (i + 1))[/code]. To remove the last element from the array, use [code]arr.resize(arr.size() - 1)[/code].
</description>
</method>
<method name="resize">
<return type="int" />
<param index="0" name="size" type="int" />
<description>
- Resizes the array to contain a different number of elements. If the array size is smaller, elements are cleared, if bigger, new elements are [code]null[/code]. Returns [constant OK] on success, or one of the other [enum Error] values if the operation failed.
- Calling [method resize] once and assigning the new values is faster than adding new elements one by one.
- [b]Note:[/b] This method acts in-place and doesn't return a modified array.
+ Sets the array's number of elements to [param size]. If [param size] is smaller than the array's current size, the elements at the end are removed. If [param size] is greater, new default elements (usually [code]null[/code]) are added, depending on the array's type.
+ Returns [constant OK] on success, or one of the other [enum Error] constants if this method fails.
+ [b]Note:[/b] Calling this method once and assigning the new values is faster than calling [method append] for every new element.
</description>
</method>
<method name="reverse">
<return type="void" />
<description>
- Reverses the order of the elements in the array.
+ Reverses the order of all elements in the array.
</description>
</method>
<method name="rfind" qualifiers="const">
@@ -596,19 +652,20 @@
<param index="0" name="what" type="Variant" />
<param index="1" name="from" type="int" default="-1" />
<description>
- Searches the array in reverse order. Optionally, a start search index can be passed. If negative, the start index is considered relative to the end of the array.
+ Returns the index of the [b]last[/b] occurrence of [param what] in this array, or [code]-1[/code] if there are none. The search's start can be specified with [param from], continuing to the beginning of the array. This method is the reverse of [method find].
</description>
</method>
<method name="shuffle">
<return type="void" />
<description>
- Shuffles the array such that the items will have a random order. This method uses the global random number generator common to methods such as [method @GlobalScope.randi]. Call [method @GlobalScope.randomize] to ensure that a new seed will be used each time if you want non-reproducible shuffling.
+ Shuffles all elements of the array in a random order.
+ [b]Note:[/b] Like many similar functions in the engine (such as [method @GlobalScope.randi] or [method pick_random]), this method uses a common, global random seed. To get a predictable outcome from this method, see [method @GlobalScope.seed].
</description>
</method>
<method name="size" qualifiers="const">
<return type="int" />
<description>
- Returns the number of elements in the array.
+ Returns the number of elements in the array. Empty arrays ([code][][/code]) always return [code]0[/code]. See also [method is_empty].
</description>
</method>
<method name="slice" qualifiers="const">
@@ -618,67 +675,71 @@
<param index="2" name="step" type="int" default="1" />
<param index="3" name="deep" type="bool" default="false" />
<description>
- Returns the slice of the [Array], from [param begin] (inclusive) to [param end] (exclusive), as a new [Array].
- The absolute value of [param begin] and [param end] will be clamped to the array size, so the default value for [param end] makes it slice to the size of the array by default (i.e. [code]arr.slice(1)[/code] is a shorthand for [code]arr.slice(1, arr.size())[/code]).
- If either [param begin] or [param end] are negative, they will be relative to the end of the array (i.e. [code]arr.slice(0, -2)[/code] is a shorthand for [code]arr.slice(0, arr.size() - 2)[/code]).
- If specified, [param step] is the relative index between source elements. It can be negative, then [param begin] must be higher than [param end]. For example, [code][0, 1, 2, 3, 4, 5].slice(5, 1, -2)[/code] returns [code][5, 3][/code].
- If [param deep] is true, each element will be copied by value rather than by reference.
- [b]Note:[/b] To include the first element when [param step] is negative, use [code]arr.slice(begin, -arr.size() - 1, step)[/code] (i.e. [code][0, 1, 2].slice(1, -4, -1)[/code] returns [code][1, 0][/code]).
+ Returns a new [Array] containing this array's elements, from index [param begin] (inclusive) to [param end] (exclusive), every [param step] elements.
+ If either [param begin] or [param end] are negative, their value is relative to the end of the array.
+ If [param step] is negative, this method iterates through the array in reverse, returning a slice ordered backwards. For this to work, [param begin] must be greater than [param end].
+ If [param deep] is [code]true[/code], all nested [Array] and [Dictionary] elements in the slice are duplicated from the original, recursively. See also [method duplicate]).
+ [codeblock]
+ var letters = ["A", "B", "C", "D", "E", "F"]
+
+ print(letters.slice(0, 2)) # Prints ["A", "B"]
+ print(letters.slice(2, -2)) # Prints ["C", "D"]
+ print(letters.slice(-2, 6)) # Prints ["E", "F"]
+
+ print(letters.slice(0, 6, 2)) # Prints ["A", "C", "E"]
+ print(letters.slice(4, 1, -1)) # Prints ["E", "D", "C"]
+ [/codeblock]
</description>
</method>
<method name="sort">
<return type="void" />
<description>
- Sorts the array.
- [b]Note:[/b] The sorting algorithm used is not [url=https://en.wikipedia.org/wiki/Sorting_algorithm#Stability]stable[/url]. This means that values considered equal may have their order changed when using [method sort].
- [b]Note:[/b] Strings are sorted in alphabetical order (as opposed to natural order). This may lead to unexpected behavior when sorting an array of strings ending with a sequence of numbers. Consider the following example:
+ Sorts the array in ascending order. The final order is dependent on the "less than" ([code]&gt;[/code]) comparison between elements.
[codeblocks]
[gdscript]
- var strings = ["string1", "string2", "string10", "string11"]
- strings.sort()
- print(strings) # Prints [string1, string10, string11, string2]
+ var numbers = [10, 5, 2.5, 8]
+ numbers.sort()
+ print(numbers) # Prints [2.5, 5, 8, 10]
[/gdscript]
[csharp]
- var strings = new Godot.Collections.Array { "string1", "string2", "string10", "string11" };
- strings.Sort();
- GD.Print(strings); // Prints [string1, string10, string11, string2]
+ var numbers = new Godot.Collections.Array { 10, 5, 2.5, 8 };
+ numbers.Sort();
+ GD.Print(numbers); // Prints [2.5, 5, 8, 10]
[/csharp]
[/codeblocks]
- To perform natural order sorting, you can use [method sort_custom] with [method String.naturalnocasecmp_to] as follows:
- [codeblock]
- var strings = ["string1", "string2", "string10", "string11"]
- strings.sort_custom(func(a, b): return a.naturalnocasecmp_to(b) &lt; 0)
- print(strings) # Prints [string1, string2, string10, string11]
- [/codeblock]
+ [b]Note:[/b] The sorting algorithm used is not [url=https://en.wikipedia.org/wiki/Sorting_algorithm#Stability]stable[/url]. This means that equivalent elements (such as [code]2[/code] and [code]2.0[/code]) may have their order changed when calling [method sort].
</description>
</method>
<method name="sort_custom">
<return type="void" />
<param index="0" name="func" type="Callable" />
<description>
- Sorts the array using a custom method. The custom method receives two arguments (a pair of elements from the array) and must return either [code]true[/code] or [code]false[/code]. For two elements [code]a[/code] and [code]b[/code], if the given method returns [code]true[/code], element [code]b[/code] will be after element [code]a[/code] in the array.
- [b]Note:[/b] The sorting algorithm used is not [url=https://en.wikipedia.org/wiki/Sorting_algorithm#Stability]stable[/url]. This means that values considered equal may have their order changed when using [method sort_custom].
- [b]Note:[/b] You cannot randomize the return value as the heapsort algorithm expects a deterministic result. Randomizing the return value will result in unexpected behavior.
- [codeblocks]
- [gdscript]
+ Sorts the array using a custom [Callable].
+ [param func] is called as many times as necessary, receiving two array elements as arguments. The function should return [code]true[/code] if the first element should be moved [i]behind[/i] the second one, otherwise it should return [code]false[/code].
+ [codeblock]
func sort_ascending(a, b):
- if a[0] &lt; b[0]:
+ if a[1] &lt; b[1]:
return true
return false
func _ready():
- var my_items = [[5, "Potato"], [9, "Rice"], [4, "Tomato"]]
+ var my_items = [["Tomato", 5], ["Apple", 9], ["Rice", 4]]
my_items.sort_custom(sort_ascending)
- print(my_items) # Prints [[4, Tomato], [5, Potato], [9, Rice]].
+ print(my_items) # Prints [["Rice", 4], ["Tomato", 5], ["Apple", 9]]
- # Descending, lambda version.
+ # Sort descending, using a lambda function.
my_items.sort_custom(func(a, b): return a[0] &gt; b[0])
- print(my_items) # Prints [[9, Rice], [5, Potato], [4, Tomato]].
- [/gdscript]
- [csharp]
- // There is no custom sort support for Godot.Collections.Array
- [/csharp]
- [/codeblocks]
+ print(my_items) # Prints [["Apple", 9], ["Tomato", 5], ["Rice", 4]]
+ [/codeblock]
+ It may also be necessary to use this method to sort strings by natural order, with [method String.naturalnocasecmp_to], as in the following example:
+ [codeblock]
+ var files = ["newfile1", "newfile2", "newfile10", "newfile11"]
+ files.sort_custom(func(a, b): return a.naturalnocasecmp_to(b) &lt; 0)
+ print(files) # Prints ["newfile1", "newfile2", "newfile10", "newfile11"]
+ [/codeblock]
+ [b]Note:[/b] In C#, this method is not supported.
+ [b]Note:[/b] The sorting algorithm used is not [url=https://en.wikipedia.org/wiki/Sorting_algorithm#Stability]stable[/url]. This means that values considered equal may have their order changed when calling this method.
+ [b]Note:[/b] You should not randomize the return value of [param func], as the heapsort algorithm expects a consistent result. Randomizing the return value will result in unexpected behavior.
</description>
</method>
</methods>
@@ -687,28 +748,44 @@
<return type="bool" />
<param index="0" name="right" type="Array" />
<description>
- Compares the left operand [Array] against the [param right] [Array]. Returns [code]true[/code] if the sizes or contents of the arrays are [i]not[/i] equal, [code]false[/code] otherwise.
+ Returns [code]true[/code] if the array's size or its elements are different than [param right]'s.
</description>
</operator>
<operator name="operator +">
<return type="Array" />
<param index="0" name="right" type="Array" />
<description>
- Concatenates two [Array]s together, with the [param right] [Array] being added to the end of the [Array] specified in the left operand. For example, [code][1, 2] + [3, 4][/code] results in [code][1, 2, 3, 4][/code].
+ Appends the [param right] array to the left operand, creating a new [Array]. This is also known as an array concatenation.
+ [codeblocks]
+ [gdscript]
+ var array1 = ["One", 2]
+ var array2 = [3, "Four"]
+ print(array1 + array2) # Prints ["One", 2, 3, "Four"]
+ [/gdscript]
+ [csharp]
+ // Note that concatenation is not possible with C#'s native Array type.
+ var array1 = new Godot.Collections.Array{"One", 2};
+ var array2 = new Godot.Collections.Array{3, "Four"};
+ GD.Print(array1 + array2); // Prints ["One", 2, 3, "Four"]
+ [/csharp]
+ [/codeblocks]
+ [b]Note:[/b] For existing arrays, [method append_array] is much more efficient than concatenation and assignment with the [code]+=[/code] operator.
</description>
</operator>
<operator name="operator &lt;">
<return type="bool" />
<param index="0" name="right" type="Array" />
<description>
- Performs a comparison for each index between the left operand [Array] and the [param right] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is less, or [code]false[/code] if the element is greater. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]false[/code] if the left operand [Array] has fewer elements, otherwise it returns [code]true[/code].
+ Compares the elements of both arrays in order, starting from index [code]0[/code] and ending on the last index in common between both arrays. For each pair of elements, returns [code]true[/code] if this array's element is less than [param right]'s, [code]false[/code] if this element is greater. Otherwise, continues to the next pair.
+ If all searched elements are equal, returns [code]true[/code] if this array's size is less than [param right]'s, otherwise returns [code]false[/code].
</description>
</operator>
<operator name="operator &lt;=">
<return type="bool" />
<param index="0" name="right" type="Array" />
<description>
- Performs a comparison for each index between the left operand [Array] and the [param right] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is less, or [code]false[/code] if the element is greater. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]true[/code] if the left operand [Array] has the same number of elements or fewer, otherwise it returns [code]false[/code].
+ Compares the elements of both arrays in order, starting from index [code]0[/code] and ending on the last index in common between both arrays. For each pair of elements, returns [code]true[/code] if this array's element is less than [param right]'s, [code]false[/code] if this element is greater. Otherwise, continues to the next pair.
+ If all searched elements are equal, returns [code]true[/code] if this array's size is less or equal to [param right]'s, otherwise returns [code]false[/code].
</description>
</operator>
<operator name="operator ==">
@@ -722,21 +799,23 @@
<return type="bool" />
<param index="0" name="right" type="Array" />
<description>
- Performs a comparison for each index between the left operand [Array] and the [param right] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is greater, or [code]false[/code] if the element is less. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]true[/code] if the [param right] [Array] has more elements, otherwise it returns [code]false[/code].
+ Compares the elements of both arrays in order, starting from index [code]0[/code] and ending on the last index in common between both arrays. For each pair of elements, returns [code]true[/code] if this array's element is greater than [param right]'s, [code]false[/code] if this element is less. Otherwise, continues to the next pair.
+ If all searched elements are equal, returns [code]true[/code] if this array's size is greater than [param right]'s, otherwise returns [code]false[/code].
</description>
</operator>
<operator name="operator &gt;=">
<return type="bool" />
<param index="0" name="right" type="Array" />
<description>
- Performs a comparison for each index between the left operand [Array] and the [param right] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is greater, or [code]false[/code] if the element is less. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]true[/code] if the [param right] [Array] has more or the same number of elements, otherwise it returns [code]false[/code].
+ Compares the elements of both arrays in order, starting from index [code]0[/code] and ending on the last index in common between both arrays. For each pair of elements, returns [code]true[/code] if this array's element is greater than [param right]'s, [code]false[/code] if this element is less. Otherwise, continues to the next pair.
+ If all searched elements are equal, returns [code]true[/code] if this array's size is greater or equal to [param right]'s, otherwise returns [code]false[/code].
</description>
</operator>
<operator name="operator []">
<return type="Variant" />
<param index="0" name="index" type="int" />
<description>
- Returns a reference to the element of type [Variant] at the specified location. Arrays start at index 0. [param index] can be a zero or positive value to start from the beginning, or a negative value to start from the end. Out-of-bounds array access causes a run-time error, which will result in an error being printed and the project execution pausing if run from the editor.
+ Returns the [Variant] element at the specified [param index]. Arrays start at index 0. If [param index] is greater or equal to [code]0[/code], the element is fetched starting from the beginning of the array. If [param index] is a negative value, the element is fetched starting from the end. Accessing an array out-of-bounds will cause a run-time error, pausing the project execution if run from the editor.
</description>
</operator>
</operators>
diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml
index 4d304cf5fd..9ef911a68d 100644
--- a/doc/classes/EditorExportPlugin.xml
+++ b/doc/classes/EditorExportPlugin.xml
@@ -36,7 +36,6 @@
<description>
Customize a resource. If changes are made to it, return the same or a new resource. Otherwise, return [code]null[/code].
The [i]path[/i] argument is only used when customizing an actual file, otherwise this means that this resource is part of another one and it will be empty.
- Calling [method skip] inside this callback will make the file not included in the export.
Implementing this method is required if [method _begin_customize_resources] returns [code]true[/code].
</description>
</method>
@@ -46,7 +45,6 @@
<param index="1" name="path" type="String" />
<description>
Customize a scene. If changes are made to it, return the same or a new scene. Otherwise, return [code]null[/code]. If a new scene is returned, it is up to you to dispose of the old one.
- Calling [method skip] inside this callback will make the file not included in the export.
Implementing this method is required if [method _begin_customize_scenes] returns [code]true[/code].
</description>
</method>
@@ -84,9 +82,8 @@
<param index="1" name="type" type="String" />
<param index="2" name="features" type="PackedStringArray" />
<description>
- Virtual method to be overridden by the user. Called for each exported file, except for imported resources (resources that have an associated [code].import[/code] file). The arguments can be used to identify the file. [param path] is the path of the file, [param type] is the [Resource] represented by the file (e.g. [PackedScene]), and [param features] is the list of features for the export.
+ Virtual method to be overridden by the user. Called for each exported file before [method _customize_resource] and [method _customize_scene]. The arguments can be used to identify the file. [param path] is the path of the file, [param type] is the [Resource] represented by the file (e.g. [PackedScene]), and [param features] is the list of features for the export.
Calling [method skip] inside this callback will make the file not included in the export.
- Use [method _customize_resource] for imported resources that are not handled by this function.
</description>
</method>
<method name="_get_android_dependencies" qualifiers="virtual const">
@@ -235,6 +232,7 @@
<description>
Adds a custom file to be exported. [param path] is the virtual path that can be used to load the file, [param file] is the binary data of the file.
When called inside [method _export_file] and [param remap] is [code]true[/code], the current file will not be exported, but instead remapped to this custom file. [param remap] is ignored when called in other places.
+ [param file] will not be imported, so consider using [method _customize_resource] to remap imported resources.
</description>
</method>
<method name="add_ios_bundle_file">
@@ -317,7 +315,7 @@
<method name="skip">
<return type="void" />
<description>
- To be called inside [method _export_file], [method _customize_resource], or [method _customize_scene]. Skips the current file, so it's not included in the export.
+ To be called inside [method _export_file]. Skips the current file, so it's not included in the export.
</description>
</method>
</methods>
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index c97ae197d9..c7ff543b66 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -396,6 +396,10 @@
<member name="editors/animation/autorename_animation_tracks" type="bool" setter="" getter="">
If [code]true[/code], automatically updates animation tracks' target paths when renaming or reparenting nodes in the Scene tree dock.
</member>
+ <member name="editors/animation/confirm_insert_track" type="bool" setter="" getter="">
+ If [code]true[/code], display a confirmation dialog when adding a new track to an animation by pressing the "key" icon next to a property. Holding Shift will bypass the dialog.
+ If [code]false[/code], the behavior is reversed, i.e. the dialog only appears when Shift is held.
+ </member>
<member name="editors/animation/default_create_bezier_tracks" type="bool" setter="" getter="">
If [code]true[/code], create a Bezier track instead of a standard track when pressing the "key" icon next to a property. Bezier tracks provide more control over animation curves, but are more difficult to adjust quickly.
</member>
diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml
index 3d048e2f63..77caea9745 100644
--- a/doc/classes/OS.xml
+++ b/doc/classes/OS.xml
@@ -283,7 +283,7 @@
<return type="String" />
<description>
Returns the file path to the current engine executable.
- [b]Note:[/b] On macOS, always use [method create_instance] instead of relying on executable path.
+ [b]Note:[/b] On macOS, if you want to launch another instance of Godot, always use [method create_instance] instead of relying on the executable path.
</description>
</method>
<method name="get_granted_permissions" qualifiers="const">
diff --git a/doc/classes/PhysicsPointQueryParameters2D.xml b/doc/classes/PhysicsPointQueryParameters2D.xml
index 521e584173..642e87947a 100644
--- a/doc/classes/PhysicsPointQueryParameters2D.xml
+++ b/doc/classes/PhysicsPointQueryParameters2D.xml
@@ -24,6 +24,7 @@
</member>
<member name="exclude" type="RID[]" setter="set_exclude" getter="get_exclude" default="[]">
The list of object [RID]s that will be excluded from collisions. Use [method CollisionObject2D.get_rid] to get the [RID] associated with a [CollisionObject2D]-derived node.
+ [b]Note:[/b] The returned array is copied and any changes to it will not update the original property value. To update the value you need to modify the returned array, and then assign it to the property again.
</member>
<member name="position" type="Vector2" setter="set_position" getter="get_position" default="Vector2(0, 0)">
The position being queried for, in global coordinates.
diff --git a/doc/classes/PhysicsPointQueryParameters3D.xml b/doc/classes/PhysicsPointQueryParameters3D.xml
index 1cbc11bd03..a53300c78d 100644
--- a/doc/classes/PhysicsPointQueryParameters3D.xml
+++ b/doc/classes/PhysicsPointQueryParameters3D.xml
@@ -20,6 +20,7 @@
</member>
<member name="exclude" type="RID[]" setter="set_exclude" getter="get_exclude" default="[]">
The list of object [RID]s that will be excluded from collisions. Use [method CollisionObject3D.get_rid] to get the [RID] associated with a [CollisionObject3D]-derived node.
+ [b]Note:[/b] The returned array is copied and any changes to it will not update the original property value. To update the value you need to modify the returned array, and then assign it to the property again.
</member>
<member name="position" type="Vector3" setter="set_position" getter="get_position" default="Vector3(0, 0, 0)">
The position being queried for, in global coordinates.
diff --git a/doc/classes/PhysicsRayQueryParameters2D.xml b/doc/classes/PhysicsRayQueryParameters2D.xml
index 3d69e092f6..a9738b4690 100644
--- a/doc/classes/PhysicsRayQueryParameters2D.xml
+++ b/doc/classes/PhysicsRayQueryParameters2D.xml
@@ -36,6 +36,7 @@
</member>
<member name="exclude" type="RID[]" setter="set_exclude" getter="get_exclude" default="[]">
The list of object [RID]s that will be excluded from collisions. Use [method CollisionObject2D.get_rid] to get the [RID] associated with a [CollisionObject2D]-derived node.
+ [b]Note:[/b] The returned array is copied and any changes to it will not update the original property value. To update the value you need to modify the returned array, and then assign it to the property again.
</member>
<member name="from" type="Vector2" setter="set_from" getter="get_from" default="Vector2(0, 0)">
The starting point of the ray being queried for, in global coordinates.
diff --git a/doc/classes/PhysicsRayQueryParameters3D.xml b/doc/classes/PhysicsRayQueryParameters3D.xml
index b203b8f555..175b594fb0 100644
--- a/doc/classes/PhysicsRayQueryParameters3D.xml
+++ b/doc/classes/PhysicsRayQueryParameters3D.xml
@@ -36,6 +36,7 @@
</member>
<member name="exclude" type="RID[]" setter="set_exclude" getter="get_exclude" default="[]">
The list of object [RID]s that will be excluded from collisions. Use [method CollisionObject3D.get_rid] to get the [RID] associated with a [CollisionObject3D]-derived node.
+ [b]Note:[/b] The returned array is copied and any changes to it will not update the original property value. To update the value you need to modify the returned array, and then assign it to the property again.
</member>
<member name="from" type="Vector3" setter="set_from" getter="get_from" default="Vector3(0, 0, 0)">
The starting point of the ray being queried for, in global coordinates.
diff --git a/doc/classes/PhysicsShapeQueryParameters2D.xml b/doc/classes/PhysicsShapeQueryParameters2D.xml
index 915d94a54c..3687a4dc5a 100644
--- a/doc/classes/PhysicsShapeQueryParameters2D.xml
+++ b/doc/classes/PhysicsShapeQueryParameters2D.xml
@@ -20,6 +20,7 @@
</member>
<member name="exclude" type="RID[]" setter="set_exclude" getter="get_exclude" default="[]">
The list of object [RID]s that will be excluded from collisions. Use [method CollisionObject2D.get_rid] to get the [RID] associated with a [CollisionObject2D]-derived node.
+ [b]Note:[/b] The returned array is copied and any changes to it will not update the original property value. To update the value you need to modify the returned array, and then assign it to the property again.
</member>
<member name="margin" type="float" setter="set_margin" getter="get_margin" default="0.0">
The collision margin for the shape.
diff --git a/doc/classes/PhysicsShapeQueryParameters3D.xml b/doc/classes/PhysicsShapeQueryParameters3D.xml
index eba2b8287f..f05322f1ab 100644
--- a/doc/classes/PhysicsShapeQueryParameters3D.xml
+++ b/doc/classes/PhysicsShapeQueryParameters3D.xml
@@ -20,6 +20,7 @@
</member>
<member name="exclude" type="RID[]" setter="set_exclude" getter="get_exclude" default="[]">
The list of object [RID]s that will be excluded from collisions. Use [method CollisionObject3D.get_rid] to get the [RID] associated with a [CollisionObject3D]-derived node.
+ [b]Note:[/b] The returned array is copied and any changes to it will not update the original property value. To update the value you need to modify the returned array, and then assign it to the property again.
</member>
<member name="margin" type="float" setter="set_margin" getter="get_margin" default="0.0">
The collision margin for the shape.
diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml
index 0f5687f091..004bbe2286 100644
--- a/doc/classes/PopupMenu.xml
+++ b/doc/classes/PopupMenu.xml
@@ -395,6 +395,12 @@
Returns [code]true[/code] if the specified item's shortcut is disabled.
</description>
</method>
+ <method name="is_native_menu" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if the system native menu is supported and currently used by this [PopupMenu].
+ </description>
+ </method>
<method name="is_system_menu" qualifiers="const">
<return type="bool" />
<description>
@@ -636,6 +642,7 @@
</member>
<member name="prefer_native_menu" type="bool" setter="set_prefer_native_menu" getter="is_prefer_native_menu" default="false">
If [code]true[/code], [MenuBar] will use native menu when supported.
+ [b]Note:[/b] If [PopupMenu] is linked to [StatusIndicator], [MenuBar], or another [PopupMenu] item it can use native menu regardless of this property, use [method is_native_menu] to check it.
</member>
<member name="submenu_popup_delay" type="float" setter="set_submenu_popup_delay" getter="get_submenu_popup_delay" default="0.3">
Sets the delay time in seconds for the submenu item to popup on mouse hovering. If the popup menu is added as a child of another (acting as a submenu), it will inherit the delay time of the parent menu item.
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index b1556783d5..b0f421e932 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -633,6 +633,9 @@
<member name="debug/settings/profiler/max_functions" type="int" setter="" getter="" default="16384">
Maximum number of functions per frame allowed when profiling.
</member>
+ <member name="debug/settings/profiler/max_timestamp_query_elements" type="int" setter="" getter="" default="256">
+ Maximum number of timestamp query elements allowed per frame for visual profiling.
+ </member>
<member name="debug/settings/stdout/print_fps" type="bool" setter="" getter="" default="false">
Print frames per second to standard output every second.
</member>
@@ -897,8 +900,8 @@
<member name="display/window/stretch/mode" type="String" setter="" getter="" default="&quot;disabled&quot;">
Defines how the base size is stretched to fit the resolution of the window or screen.
[b]"disabled"[/b]: No stretching happens. One unit in the scene corresponds to one pixel on the screen. In this mode, [member display/window/stretch/aspect] has no effect. Recommended for non-game applications.
- [b]"canvas_items"[/b]: The base size specified in width and height in the project settings is stretched to cover the whole screen (taking [member display/window/stretch/aspect] into account). This means that everything is rendered directly at the target resolution. 3D is unaffected, while in 2D, there is no longer a 1:1 correspondence between sprite pixels and screen pixels, which may result in scaling artifacts. Recommended for most games that don't use a pixel art esthetic, although it is possible to use this stretch mode for pixel art games too (especially in 3D).
- [b]"viewport"[/b]: The size of the root [Viewport] is set precisely to the base size specified in the Project Settings' Display section. The scene is rendered to this viewport first. Finally, this viewport is scaled to fit the screen (taking [member display/window/stretch/aspect] into account). Recommended for games that use a pixel art esthetic.
+ [b]"canvas_items"[/b]: The base size specified in width and height in the project settings is stretched to cover the whole screen (taking [member display/window/stretch/aspect] into account). This means that everything is rendered directly at the target resolution. 3D is unaffected, while in 2D, there is no longer a 1:1 correspondence between sprite pixels and screen pixels, which may result in scaling artifacts. Recommended for most games that don't use a pixel art aesthetic, although it is possible to use this stretch mode for pixel art games too (especially in 3D).
+ [b]"viewport"[/b]: The size of the root [Viewport] is set precisely to the base size specified in the Project Settings' Display section. The scene is rendered to this viewport first. Finally, this viewport is scaled to fit the screen (taking [member display/window/stretch/aspect] into account). Recommended for games that use a pixel art aesthetic.
</member>
<member name="display/window/stretch/scale" type="float" setter="" getter="" default="1.0">
The scale factor multiplier to use for 2D elements. This multiplies the final scale factor determined by [member display/window/stretch/mode]. If using the [b]Disabled[/b] stretch mode, this scale factor is applied as-is. This can be adjusted to make the UI easier to read on certain displays.
@@ -931,8 +934,9 @@
Changing this value allows setting up a multi-project scenario where there are multiple [code].csproj[/code]. Keep in mind that the Godot project is considered one of the C# projects in the workspace and it's root directory should contain the [code]project.godot[/code] and [code].csproj[/code] next to each other.
</member>
<member name="editor/export/convert_text_resources_to_binary" type="bool" setter="" getter="" default="true">
- If [code]true[/code], text resources are converted to a binary format on export. This decreases file sizes and speeds up loading slightly.
- [b]Note:[/b] If [member editor/export/convert_text_resources_to_binary] is [code]true[/code], [method @GDScript.load] will not be able to return the converted files in an exported project. Some file paths within the exported PCK will also change, such as [code]project.godot[/code] becoming [code]project.binary[/code]. If you rely on run-time loading of files present within the PCK, set [member editor/export/convert_text_resources_to_binary] to [code]false[/code].
+ If [code]true[/code], text resource ([code]tres[/code]) and text scene ([code]tscn[/code]) files are converted to their corresponding binary format on export. This decreases file sizes and speeds up loading slightly.
+ [b]Note:[/b] Because a resource's file extension may change in an exported project, it is heavily recommended to use [method @GDScript.load] or [ResourceLoader] instead of [FileAccess] to load resources dynamically.
+ [b]Note:[/b] The project settings file ([code]project.godot[/code]) will always be converted to binary on export, regardless of this setting.
</member>
<member name="editor/import/atlas_max_width" type="int" setter="" getter="" default="2048">
The maximum width to use when importing textures as an atlas. The value will be rounded to the nearest power of two when used. Use this to prevent imported textures from growing too large in the other direction.
@@ -1391,6 +1395,12 @@
Enabling this can greatly improve the responsiveness to input, specially in devices that need to run multiple physics frames per visible (process) frame, because they can't run at the target frame rate.
[b]Note:[/b] Currently implemented only on Android.
</member>
+ <member name="input_devices/buffering/android/use_accumulated_input" type="bool" setter="" getter="" default="true">
+ If [code]true[/code], multiple input events will be accumulated into a single input event when possible.
+ </member>
+ <member name="input_devices/buffering/android/use_input_buffering" type="bool" setter="" getter="" default="true">
+ If [code]true[/code], input events will be buffered prior to being dispatched.
+ </member>
<member name="input_devices/compatibility/legacy_just_pressed_behavior" type="bool" setter="" getter="" default="false">
If [code]true[/code], [method Input.is_action_just_pressed] and [method Input.is_action_just_released] will only return [code]true[/code] if the action is still in the respective state, i.e. an action that is pressed [i]and[/i] released on the same frame will be missed.
If [code]false[/code], no input will be lost.
diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml
index 24d2d26beb..7e0c39ac7c 100644
--- a/doc/classes/RichTextLabel.xml
+++ b/doc/classes/RichTextLabel.xml
@@ -432,7 +432,7 @@
Adds [code skip-lint][ol][/code] or [code skip-lint][ul][/code] tag to the tag stack. Multiplies [param level] by current [member tab_size] to determine new margin length.
</description>
</method>
- <method name="push_meta">
+ <method name="push_meta" keywords="push_url">
<return type="void" />
<param index="0" name="data" type="Variant" />
<param index="1" name="underline_mode" type="int" enum="RichTextLabel.MetaUnderline" default="1" />
@@ -628,7 +628,7 @@
<member name="language" type="String" setter="set_language" getter="get_language" default="&quot;&quot;">
Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead.
</member>
- <member name="meta_underlined" type="bool" setter="set_meta_underline" getter="is_meta_underlined" default="true">
+ <member name="meta_underlined" type="bool" setter="set_meta_underline" getter="is_meta_underlined" default="true" keywords="url_underlined">
If [code]true[/code], the label underlines meta tags such as [code skip-lint][url]{text}[/url][/code]. These tags can call a function when clicked if [signal meta_clicked] is connected to a function.
</member>
<member name="progress_bar_delay" type="int" setter="set_progress_bar_delay" getter="get_progress_bar_delay" default="1000">
diff --git a/doc/classes/ScriptEditorBase.xml b/doc/classes/ScriptEditorBase.xml
index 403608355a..638bc921d6 100644
--- a/doc/classes/ScriptEditorBase.xml
+++ b/doc/classes/ScriptEditorBase.xml
@@ -72,7 +72,7 @@
</description>
</signal>
<signal name="request_save_previous_state">
- <param index="0" name="line" type="int" />
+ <param index="0" name="state" type="Dictionary" />
<description>
Emitted when the user changes current script or moves caret by 10 or more columns within the same script.
</description>
diff --git a/doc/classes/Transform2D.xml b/doc/classes/Transform2D.xml
index 345d0512ff..4158fbe710 100644
--- a/doc/classes/Transform2D.xml
+++ b/doc/classes/Transform2D.xml
@@ -4,8 +4,10 @@
A 2×3 matrix representing a 2D transformation.
</brief_description>
<description>
- A 2×3 matrix (2 rows, 3 columns) used for 2D linear transformations. It can represent transformations such as translation, rotation, and scaling. It consists of three [Vector2] values: [member x], [member y], and the [member origin].
+ The [Transform2D] built-in [Variant] type is a 2×3 [url=https://en.wikipedia.org/wiki/Matrix_(mathematics)]matrix[/url] representing a transformation in 2D space. It contains three [Vector2] values: [member x], [member y], and [member origin]. Together, they can represent translation, rotation, scale, and skew.
+ The [member x] and [member y] axes form a 2×2 matrix, known as the transform's [b]basis[/b]. The length of each axis ([method Vector2.length]) influences the transform's scale, while the direction of all axes influence the rotation. Usually, both axes are perpendicular to one another. However, when you rotate one axis individually, the transform becomes skewed. Applying a skewed transform to a 2D sprite will make the sprite appear distorted.
For a general introduction, see the [url=$DOCS_URL/tutorials/math/matrices_and_transforms.html]Matrices and transforms[/url] tutorial.
+ [b]Note:[/b] Unlike [Transform3D], there is no 2D equivalent to the [Basis] type. All mentions of "basis" refer to the [member x] and [member y] components of [Transform2D].
</description>
<tutorials>
<link title="Math documentation index">$DOCS_URL/tutorials/math/index.html</link>
@@ -17,7 +19,7 @@
<constructor name="Transform2D">
<return type="Transform2D" />
<description>
- Constructs a default-initialized [Transform2D] set to [constant IDENTITY].
+ Constructs a [Transform2D] identical to [constant IDENTITY].
</description>
</constructor>
<constructor name="Transform2D">
@@ -32,7 +34,7 @@
<param index="0" name="rotation" type="float" />
<param index="1" name="position" type="Vector2" />
<description>
- Constructs the transform from a given angle (in radians) and position.
+ Constructs a [Transform2D] from a given angle (in radians) and position.
</description>
</constructor>
<constructor name="Transform2D">
@@ -42,7 +44,7 @@
<param index="2" name="skew" type="float" />
<param index="3" name="position" type="Vector2" />
<description>
- Constructs the transform from a given angle (in radians), scale, skew (in radians) and position.
+ Constructs a [Transform2D] from a given angle (in radians), scale, skew (in radians), and position.
</description>
</constructor>
<constructor name="Transform2D">
@@ -51,7 +53,7 @@
<param index="1" name="y_axis" type="Vector2" />
<param index="2" name="origin" type="Vector2" />
<description>
- Constructs the transform from 3 [Vector2] values representing [member x], [member y], and the [member origin] (the three column vectors).
+ Constructs a [Transform2D] from 3 [Vector2] values representing [member x], [member y], and the [member origin] (the three matrix columns).
</description>
</constructor>
</constructors>
@@ -59,56 +61,81 @@
<method name="affine_inverse" qualifiers="const">
<return type="Transform2D" />
<description>
- Returns the inverse of the transform, under the assumption that the basis is invertible (must have non-zero determinant).
+ Returns the inverted version of this transform. Unlike [method inverse], this method works with almost any basis, including non-uniform ones, but is slower. See also [method inverse].
+ [b]Note:[/b] For this method to return correctly, the transform's basis needs to have a determinant that is not exactly [code]0[/code] (see [method determinant]).
</description>
</method>
<method name="basis_xform" qualifiers="const">
<return type="Vector2" />
<param index="0" name="v" type="Vector2" />
<description>
- Returns a vector transformed (multiplied) by the basis matrix.
- This method does not account for translation (the [member origin] vector).
+ Returns a copy of the [param v] vector, transformed (multiplied) by the transform basis's matrix. Unlike the multiplication operator ([code]*[/code]), this method ignores the [member origin].
</description>
</method>
<method name="basis_xform_inv" qualifiers="const">
<return type="Vector2" />
<param index="0" name="v" type="Vector2" />
<description>
- Returns a vector transformed (multiplied) by the inverse basis matrix, under the assumption that the basis is orthonormal (i.e. rotation/reflection is fine, scaling/skew is not).
- This method does not account for translation (the [member origin] vector).
- [code]transform.basis_xform_inv(vector)[/code] is equivalent to [code]transform.inverse().basis_xform(vector)[/code]. See [method inverse].
- For non-orthonormal transforms (e.g. with scaling) [code]transform.affine_inverse().basis_xform(vector)[/code] can be used instead. See [method affine_inverse].
+ Returns a copy of the [param v] vector, transformed (multiplied) by the inverse transform basis's matrix (see [method inverse]). This method ignores the [member origin].
+ [b]Note:[/b] This method assumes that this transform's basis is [i]orthonormal[/i] (see [method orthonormalized]). If the basis is not orthonormal, [code]transform.affine_inverse().basis_xform(vector)[/code] should be used instead (see [method affine_inverse]).
</description>
</method>
<method name="determinant" qualifiers="const">
<return type="float" />
<description>
- Returns the determinant of the basis matrix. If the basis is uniformly scaled, then its determinant equals the square of the scale factor.
- A negative determinant means the basis was flipped, so one part of the scale is negative. A zero determinant means the basis isn't invertible, and is usually considered invalid.
+ Returns the [url=https://en.wikipedia.org/wiki/Determinant]determinant[/url] of this transform basis's matrix. For advanced math, this number can be used to determine a few attributes:
+ - If the determinant is exactly [code]0[/code], the basis is not invertible (see [method inverse]).
+ - If the determinant is a negative number, the basis represents a negative scale.
+ [b]Note:[/b] If the basis's scale is the same for every axis, its determinant is always that scale by the power of 2.
</description>
</method>
<method name="get_origin" qualifiers="const">
<return type="Vector2" />
<description>
- Returns the transform's origin (translation).
+ Returns this transform's translation. Equivalent to [member origin].
</description>
</method>
<method name="get_rotation" qualifiers="const">
<return type="float" />
<description>
- Returns the transform's rotation (in radians).
+ Returns this transform's rotation (in radians). This is equivalent to [member x]'s angle (see [method Vector2.angle]).
</description>
</method>
<method name="get_scale" qualifiers="const">
<return type="Vector2" />
<description>
- Returns the scale.
+ Returns the length of both [member x] and [member y], as a [Vector2]. If this transform's basis is not skewed, this value is the scaling factor. It is not affected by rotation.
+ [codeblocks]
+ [gdscript]
+ var my_transform = Transform2D(
+ Vector2(2, 0),
+ Vector2(0, 4),
+ Vector2(0, 0)
+ )
+ # Rotating the Transform2D in any way preserves its scale.
+ my_transform = my_transform.rotated(TAU / 2)
+
+ print(my_transform.get_scale()) # Prints (2, 4).
+ [/gdscript]
+ [csharp]
+ var myTransform = new Transform2D(
+ Vector3(2.0f, 0.0f),
+ Vector3(0.0f, 4.0f),
+ Vector3(0.0f, 0.0f)
+ );
+ // Rotating the Transform2D in any way preserves its scale.
+ myTransform = myTransform.Rotated(Mathf.Tau / 2.0f);
+
+ GD.Print(myTransform.GetScale()); // Prints (2, 4, 8).
+ [/csharp]
+ [/codeblocks]
+ [b]Note:[/b] If the value returned by [method determinant] is negative, the scale is also negative.
</description>
</method>
<method name="get_skew" qualifiers="const">
<return type="float" />
<description>
- Returns the transform's skew (in radians).
+ Returns this transform's skew (in radians).
</description>
</method>
<method name="interpolate_with" qualifiers="const">
@@ -116,19 +143,21 @@
<param index="0" name="xform" type="Transform2D" />
<param index="1" name="weight" type="float" />
<description>
- Returns a transform interpolated between this transform and another by a given [param weight] (on the range of 0.0 to 1.0).
+ Returns the result of the linear interpolation between this transform and [param xform] by the given [param weight].
+ The [param weight] should be between [code]0.0[/code] and [code]1.0[/code] (inclusive). Values outside this range are allowed and can be used to perform [i]extrapolation[/i] instead.
</description>
</method>
<method name="inverse" qualifiers="const">
<return type="Transform2D" />
<description>
- Returns the inverse of the transform, under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection is fine, scaling/skew is not). Use [method affine_inverse] for non-orthonormal transforms (e.g. with scaling).
+ Returns the [url=https://en.wikipedia.org/wiki/Invertible_matrix]inverted version of this transform[/url].
+ [b]Note:[/b] For this method to return correctly, the transform's basis needs to be [i]orthonormal[/i] (see [method orthonormalized]). That means, the basis should only represent a rotation. If it does not, use [method affine_inverse] instead.
</description>
</method>
<method name="is_conformal" qualifiers="const">
<return type="bool" />
<description>
- Returns [code]true[/code] if the transform's basis is conformal, meaning it preserves angles and distance ratios, and may only be composed of rotation and uniform scale. Returns [code]false[/code] if the transform's basis has non-uniform scale or shear/skew. This can be used to validate if the transform is non-distorted, which is important for physics and other use cases.
+ Returns [code]true[/code] if this transform's basis is conformal. A conformal basis is both [i]orthogonal[/i] (the axes are perpendicular to each other) and [i]uniform[/i] (the axes share the same length). This method can be especially useful during physics calculations.
</description>
</method>
<method name="is_equal_approx" qualifiers="const">
@@ -148,14 +177,13 @@
<return type="Transform2D" />
<param index="0" name="target" type="Vector2" default="Vector2(0, 0)" />
<description>
- Returns a copy of the transform rotated such that the rotated X-axis points towards the [param target] position.
- Operations take place in global space.
+ Returns a copy of the transform rotated such that the rotated X-axis points towards the [param target] position, in global space.
</description>
</method>
<method name="orthonormalized" qualifiers="const">
<return type="Transform2D" />
<description>
- Returns the transform with the basis orthogonal (90 degrees), and normalized axis vectors (scale of 1 or -1).
+ Returns a copy of this transform with its basis orthonormalized. An orthonormal basis is both [i]orthogonal[/i] (the axes are perpendicular to each other) and [i]normalized[/i] (the axes have a length of [code]1[/code]), which also means it can only represent rotation.
</description>
</method>
<method name="rotated" qualifiers="const">
@@ -215,24 +243,41 @@
</methods>
<members>
<member name="origin" type="Vector2" setter="" getter="" default="Vector2(0, 0)">
- The origin vector (column 2, the third column). Equivalent to array index [code]2[/code]. The origin vector represents translation.
+ The translation offset of this transform, and the column [code]2[/code] of the matrix. In 2D space, this can be seen as the position.
</member>
<member name="x" type="Vector2" setter="" getter="" default="Vector2(1, 0)">
- The basis matrix's X vector (column 0). Equivalent to array index [code]0[/code].
+ The transform basis's X axis, and the column [code]0[/code] of the matrix. Combined with [member y], this represents the transform's rotation, scale, and skew.
+ On the identity transform, this vector points right ([constant Vector2.RIGHT]).
</member>
<member name="y" type="Vector2" setter="" getter="" default="Vector2(0, 1)">
- The basis matrix's Y vector (column 1). Equivalent to array index [code]1[/code].
+ The transform basis's Y axis, and the column [code]1[/code] of the matrix. Combined with [member x], this represents the transform's rotation, scale, and skew.
+ On the identity transform, this vector points up ([constant Vector2.UP]).
</member>
</members>
<constants>
<constant name="IDENTITY" value="Transform2D(1, 0, 0, 1, 0, 0)">
- The identity [Transform2D] with no translation, rotation or scaling applied. When applied to other data structures, [constant IDENTITY] performs no transformation.
+ The identity [Transform2D]. A transform with no translation, no rotation, and its scale being [code]1[/code]. When multiplied by another [Variant] such as [Rect2] or another [Transform2D], no transformation occurs. This means that:
+ - The [member x] points right ([constant Vector2.RIGHT]);
+ - The [member y] points up ([constant Vector2.UP]).
+ [codeblock]
+ var transform = Transform2D.IDENTITY
+ print("| X | Y | Origin")
+ print("| %s | %s | %s" % [transform.x.x, transform.y.x, transform.origin.x])
+ print("| %s | %s | %s" % [transform.x.y, transform.y.y, transform.origin.y])
+ # Prints:
+ # | X | Y | Origin
+ # | 1 | 0 | 0
+ # | 0 | 1 | 0
+ [/codeblock]
+ This is identical to creating [constructor Transform2D] without any parameters. This constant can be used to make your code clearer, and for consistency with C#.
</constant>
<constant name="FLIP_X" value="Transform2D(-1, 0, 0, 1, 0, 0)">
- The [Transform2D] that will flip something along the X axis.
+ When any transform is multiplied by [constant FLIP_X], it negates all components of the [member x] axis (the X column).
+ When [constant FLIP_X] is multiplied by any basis, it negates the [member Vector2.x] component of all axes (the X row).
</constant>
<constant name="FLIP_Y" value="Transform2D(1, 0, 0, -1, 0, 0)">
- The [Transform2D] that will flip something along the Y axis.
+ When any transform is multiplied by [constant FLIP_Y], it negates all components of the [member y] axis (the Y column).
+ When [constant FLIP_Y] is multiplied by any basis, it negates the [member Vector2.y] component of all axes (the Y row).
</constant>
</constants>
<operators>
@@ -240,7 +285,7 @@
<return type="bool" />
<param index="0" name="right" type="Transform2D" />
<description>
- Returns [code]true[/code] if the transforms are not equal.
+ Returns [code]true[/code] if the components of both transforms are not equal.
[b]Note:[/b] Due to floating-point precision errors, consider using [method is_equal_approx] instead, which is more reliable.
</description>
</operator>
@@ -248,63 +293,69 @@
<return type="PackedVector2Array" />
<param index="0" name="right" type="PackedVector2Array" />
<description>
- Transforms (multiplies) each element of the [Vector2] array by the given [Transform2D] matrix.
+ Transforms (multiplies) every [Vector2] element of the given [PackedVector2Array] by this transformation matrix.
+ On larger arrays, this operation is much faster than transforming each [Vector2] individually.
</description>
</operator>
<operator name="operator *">
<return type="Rect2" />
<param index="0" name="right" type="Rect2" />
<description>
- Transforms (multiplies) the [Rect2] by the given [Transform2D] matrix.
+ Transforms (multiplies) the [Rect2] by this transformation matrix.
</description>
</operator>
<operator name="operator *">
<return type="Transform2D" />
<param index="0" name="right" type="Transform2D" />
<description>
- Composes these two transformation matrices by multiplying them together. This has the effect of transforming the second transform (the child) by the first transform (the parent).
+ Transforms (multiplies) this transform by the [param right] transform.
+ This is the operation performed between parent and child [CanvasItem] nodes.
+ [b]Note:[/b] If you need to only modify one attribute of this transform, consider using one of the following methods, instead:
+ - For translation, see [method translated] or [method translated_local].
+ - For rotation, see [method rotated] or [method rotated_local].
+ - For scale, see [method scaled] or [method scaled_local].
</description>
</operator>
<operator name="operator *">
<return type="Vector2" />
<param index="0" name="right" type="Vector2" />
<description>
- Transforms (multiplies) the [Vector2] by the given [Transform2D] matrix.
+ Transforms (multiplies) the [Vector2] by this transformation matrix.
</description>
</operator>
<operator name="operator *">
<return type="Transform2D" />
<param index="0" name="right" type="float" />
<description>
- This operator multiplies all components of the [Transform2D], including the [member origin] vector, which scales it uniformly.
+ Multiplies all components of the [Transform2D] by the given [float], including the [member origin]. This affects the transform's scale uniformly.
</description>
</operator>
<operator name="operator *">
<return type="Transform2D" />
<param index="0" name="right" type="int" />
<description>
- This operator multiplies all components of the [Transform2D], including the [member origin] vector, which scales it uniformly.
+ Multiplies all components of the [Transform2D] by the given [int], including the [member origin]. This affects the transform's scale uniformly.
</description>
</operator>
<operator name="operator /">
<return type="Transform2D" />
<param index="0" name="right" type="float" />
<description>
- This operator divides all components of the [Transform2D], including the [member origin] vector, which inversely scales it uniformly.
+ Divides all components of the [Transform2D] by the given [float], including the [member origin]. This affects the transform's scale uniformly.
</description>
</operator>
<operator name="operator /">
<return type="Transform2D" />
<param index="0" name="right" type="int" />
<description>
- This operator divides all components of the [Transform2D], including the [member origin] vector, which inversely scales it uniformly.
+ Divides all components of the [Transform2D] by the given [int], including the [member origin]. This affects the transform's scale uniformly.
</description>
</operator>
<operator name="operator ==">
<return type="bool" />
<param index="0" name="right" type="Transform2D" />
<description>
- Returns [code]true[/code] if the transforms are exactly equal.
+ Returns [code]true[/code] if the components of both transforms are exactly equal.
[b]Note:[/b] Due to floating-point precision errors, consider using [method is_equal_approx] instead, which is more reliable.
</description>
</operator>
@@ -312,7 +363,7 @@
<return type="Vector2" />
<param index="0" name="index" type="int" />
<description>
- Access transform components using their index. [code]t[0][/code] is equivalent to [code]t.x[/code], [code]t[1][/code] is equivalent to [code]t.y[/code], and [code]t[2][/code] is equivalent to [code]t.origin[/code].
+ Accesses each axis (column) of this transform by their index. Index [code]0[/code] is the same as [member x], index [code]1[/code] is the same as [member y], and index [code]2[/code] is the same as [member origin].
</description>
</operator>
</operators>
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index 8e89889fd1..a6796a1a6b 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -2499,7 +2499,9 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
glColorMask(0, 0, 0, 0);
RasterizerGLES3::clear_depth(0.0);
glClear(GL_DEPTH_BUFFER_BIT);
- glDrawBuffers(0, nullptr);
+ // Some desktop GL implementations fall apart when using Multiview with GL_NONE.
+ GLuint db = p_camera_data->view_count > 1 ? GL_COLOR_ATTACHMENT0 : GL_NONE;
+ glDrawBuffers(1, &db);
uint64_t spec_constant = SceneShaderGLES3::DISABLE_FOG | SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL |
SceneShaderGLES3::DISABLE_LIGHTMAP | SceneShaderGLES3::DISABLE_LIGHT_OMNI |
diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h
index 8a03d72b9b..1b83efee32 100644
--- a/drivers/gles3/storage/texture_storage.h
+++ b/drivers/gles3/storage/texture_storage.h
@@ -169,7 +169,7 @@ struct Texture {
TYPE_3D
};
- Type type;
+ Type type = TYPE_2D;
RS::TextureLayeredType layered_type = RS::TEXTURE_LAYERED_2D_ARRAY;
GLenum target = GL_TEXTURE_2D;
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp
index ad7598202f..48b9e01fd8 100644
--- a/editor/animation_bezier_editor.cpp
+++ b/editor/animation_bezier_editor.cpp
@@ -965,7 +965,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
real_t minimum_value = INFINITY;
real_t maximum_value = -INFINITY;
- for (const IntPair &E : selection) {
+ for (const IntPair &E : focused_keys) {
IntPair key_pair = E;
real_t time = animation->track_get_key_time(key_pair.first, key_pair.second);
@@ -1096,7 +1096,8 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
for (int i = 0; i < animation->track_get_key_count(track); ++i) {
undo_redo->add_undo_method(
this,
- "_bezier_track_insert_key",
+ "_bezier_track_insert_key_at_anim",
+ animation,
track,
animation->track_get_key_time(track, i),
animation->bezier_track_get_key_value(track, i),
@@ -1220,7 +1221,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
//insert new point
if (mb->get_position().x >= limit && mb->get_position().x < get_size().width && mb->is_command_or_control_pressed()) {
float h = (get_size().height / 2.0 - mb->get_position().y) * timeline_v_zoom + timeline_v_scroll;
- Array new_point = make_default_bezier_key(h);
+ Array new_point = animation->make_default_bezier_key(h);
real_t time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value();
while (animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX) != -1) {
@@ -1370,7 +1371,8 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
key[0] = h;
undo_redo->add_do_method(
this,
- "_bezier_track_insert_key",
+ "_bezier_track_insert_key_at_anim",
+ animation,
E->get().first,
newpos,
key[0],
@@ -1391,7 +1393,8 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
Array key = animation->track_get_key_value(E->get().first, E->get().second);
undo_redo->add_undo_method(
this,
- "_bezier_track_insert_key",
+ "_bezier_track_insert_key_at_anim",
+ animation,
E->get().first,
oldpos,
key[0],
@@ -1409,7 +1412,8 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, 1);
undo_redo->add_undo_method(
this,
- "_bezier_track_insert_key",
+ "_bezier_track_insert_key_at_anim",
+ animation,
amr.track,
amr.time,
key[0],
@@ -1643,19 +1647,6 @@ void AnimationBezierTrackEdit::_zoom_callback(float p_zoom_factor, Vector2 p_ori
queue_redraw();
}
-Array AnimationBezierTrackEdit::make_default_bezier_key(float p_value) {
- Array new_point;
- new_point.resize(5);
-
- new_point[0] = p_value;
- new_point[1] = -0.25;
- new_point[2] = 0;
- new_point[3] = 0.25;
- new_point[4] = 0;
-
- return new_point;
-}
-
float AnimationBezierTrackEdit::get_bezier_key_value(Array p_bezier_key_array) {
return p_bezier_key_array[0];
}
@@ -1675,7 +1666,7 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
time += 0.001;
}
float h = (get_size().height / 2.0 - menu_insert_key.y) * timeline_v_zoom + timeline_v_scroll;
- Array new_point = make_default_bezier_key(h);
+ Array new_point = animation->make_default_bezier_key(h);
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Add Bezier Point"));
undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point);
@@ -1877,7 +1868,7 @@ void AnimationBezierTrackEdit::paste_keys(real_t p_ofs, bool p_ofs_valid) {
Variant value = key.value;
if (key.track_type != Animation::TYPE_BEZIER) {
- value = make_default_bezier_key(key.value);
+ value = animation->make_default_bezier_key(key.value);
}
undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, dst_time, value, key.transition);
@@ -1931,10 +1922,9 @@ void AnimationBezierTrackEdit::delete_selection() {
}
}
-void AnimationBezierTrackEdit::_bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode) {
- ERR_FAIL_COND(animation.is_null());
- int idx = animation->bezier_track_insert_key(p_track, p_time, p_value, p_in_handle, p_out_handle);
- animation->bezier_track_set_key_handle_mode(p_track, idx, p_handle_mode);
+void AnimationBezierTrackEdit::_bezier_track_insert_key_at_anim(const Ref<Animation> &p_anim, int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode) {
+ int idx = p_anim->bezier_track_insert_key(p_track, p_time, p_value, p_in_handle, p_out_handle);
+ p_anim->bezier_track_set_key_handle_mode(p_track, idx, p_handle_mode);
}
void AnimationBezierTrackEdit::_bind_methods() {
@@ -1943,7 +1933,7 @@ void AnimationBezierTrackEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("_select_at_anim"), &AnimationBezierTrackEdit::_select_at_anim);
ClassDB::bind_method(D_METHOD("_update_hidden_tracks_after"), &AnimationBezierTrackEdit::_update_hidden_tracks_after);
ClassDB::bind_method(D_METHOD("_update_locked_tracks_after"), &AnimationBezierTrackEdit::_update_locked_tracks_after);
- ClassDB::bind_method(D_METHOD("_bezier_track_insert_key"), &AnimationBezierTrackEdit::_bezier_track_insert_key);
+ ClassDB::bind_method(D_METHOD("_bezier_track_insert_key_at_anim"), &AnimationBezierTrackEdit::_bezier_track_insert_key_at_anim);
ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"), PropertyInfo(Variant::INT, "track")));
ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::INT, "track")));
diff --git a/editor/animation_bezier_editor.h b/editor/animation_bezier_editor.h
index 888dad5341..dd7e8758f3 100644
--- a/editor/animation_bezier_editor.h
+++ b/editor/animation_bezier_editor.h
@@ -198,7 +198,6 @@ protected:
void _notification(int p_what);
public:
- static Array make_default_bezier_key(float p_value);
static float get_bezier_key_value(Array p_bezier_key_array);
virtual String get_tooltip(const Point2 &p_pos) const override;
@@ -222,7 +221,7 @@ public:
void paste_keys(real_t p_ofs, bool p_ofs_valid);
void delete_selection();
- void _bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode);
+ void _bezier_track_insert_key_at_anim(const Ref<Animation> &p_anim, int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode);
AnimationBezierTrackEdit();
};
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index a3d0dfb89b..b6636ca576 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -3690,7 +3690,7 @@ void AnimationTrackEditor::_name_limit_changed() {
}
void AnimationTrackEditor::_timeline_changed(float p_new_pos, bool p_timeline_only) {
- emit_signal(SNAME("timeline_changed"), p_new_pos, p_timeline_only);
+ emit_signal(SNAME("timeline_changed"), p_new_pos, p_timeline_only, false);
}
void AnimationTrackEditor::_track_remove_request(int p_track) {
@@ -3787,6 +3787,7 @@ void AnimationTrackEditor::set_anim_pos(float p_pos) {
}
_redraw_groups();
bezier_edit->set_play_position(p_pos);
+ emit_signal(SNAME("timeline_changed"), p_pos, true, true);
}
static bool track_type_is_resettable(Animation::TrackType p_type) {
@@ -3867,15 +3868,23 @@ void AnimationTrackEditor::commit_insert_queue() {
}
// Skip the confirmation dialog if the user holds Shift while clicking the key icon.
- if (!Input::get_singleton()->is_key_pressed(Key::SHIFT) && num_tracks > 0) {
- String shortcut_hint = TTR("Hold Shift when clicking the key icon to skip this dialog.");
+ // If `confirm_insert_track` editor setting is disabled, the behavior is reversed.
+ bool confirm_insert = EDITOR_GET("editors/animation/confirm_insert_track");
+ if ((Input::get_singleton()->is_key_pressed(Key::SHIFT) != confirm_insert) && num_tracks > 0) {
+ String dialog_text;
+
// Potentially a new key, does not exist.
if (num_tracks == 1) {
// TRANSLATORS: %s will be replaced by a phrase describing the target of track.
- insert_confirm_text->set_text(vformat(TTR("Create new track for %s and insert key?") + "\n\n" + shortcut_hint, last_track_query));
+ dialog_text = vformat(TTR("Create new track for %s and insert key?"), last_track_query);
} else {
- insert_confirm_text->set_text(vformat(TTR("Create %d new tracks and insert keys?") + "\n\n" + shortcut_hint, num_tracks));
+ dialog_text = vformat(TTR("Create %d new tracks and insert keys?"), num_tracks);
+ }
+
+ if (confirm_insert) {
+ dialog_text += +"\n\n" + TTR("Hold Shift when clicking the key icon to skip this dialog.");
}
+ insert_confirm_text->set_text(dialog_text);
insert_confirm_bezier->set_visible(all_bezier);
insert_confirm_reset->set_visible(reset_allowed);
@@ -4420,7 +4429,7 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD
for (int i = 0; i < subindices.size(); i++) {
InsertData id = p_id;
id.type = Animation::TYPE_BEZIER;
- id.value = p_id.value.get(subindices[i].substr(1, subindices[i].length()));
+ id.value = subindices[i].is_empty() ? p_id.value : p_id.value.get(subindices[i].substr(1, subindices[i].length()));
id.path = String(p_id.path) + subindices[i];
p_next_tracks = _confirm_insert(id, p_next_tracks, p_reset_wanted, p_reset_anim, false);
}
@@ -4456,16 +4465,8 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD
} break;
case Animation::TYPE_BEZIER: {
- Array array;
- array.resize(5);
- array[0] = p_id.value;
- array[1] = -0.25;
- array[2] = 0;
- array[3] = 0.25;
- array[4] = 0;
- value = array;
+ value = animation->make_default_bezier_key(p_id.value);
bezier_edit_icon->set_disabled(false);
-
} break;
default: {
// Other track types shouldn't use this code path.
@@ -5267,15 +5268,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
NodePath bp;
Variant value;
_find_hint_for_track(p_track, bp, &value);
- Array arr;
- arr.resize(5);
- arr[0] = value;
- arr[1] = -0.25;
- arr[2] = 0;
- arr[3] = 0.25;
- arr[4] = 0;
-
- id.value = arr;
+ id.value = animation->make_default_bezier_key(value);
} break;
case Animation::TYPE_AUDIO: {
Dictionary ak;
@@ -5819,7 +5812,7 @@ void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, bool p_ofs_valid, i
if (key_is_bezier && !track_is_bezier) {
value = AnimationBezierTrackEdit::get_bezier_key_value(value);
} else if (!key_is_bezier && track_is_bezier) {
- value = AnimationBezierTrackEdit::make_default_bezier_key(value);
+ value = animation->make_default_bezier_key(value);
}
undo_redo->add_do_method(animation.ptr(), "track_insert_key", dst_track, dst_time, value, animation->track_get_key_transition(E->key().track, E->key().key));
@@ -5963,7 +5956,7 @@ void AnimationTrackEditor::_anim_paste_keys(float p_ofs, bool p_ofs_valid, int p
if (key_is_bezier && !track_is_bezier) {
value = AnimationBezierTrackEdit::get_bezier_key_value(value);
} else if (!key_is_bezier && track_is_bezier) {
- value = AnimationBezierTrackEdit::make_default_bezier_key(value);
+ value = animation->make_default_bezier_key(value);
}
undo_redo->add_do_method(animation.ptr(), "track_insert_key", dst_track, dst_time, value, key.transition);
diff --git a/editor/editor_dock_manager.cpp b/editor/editor_dock_manager.cpp
index ccb47220db..75135532aa 100644
--- a/editor/editor_dock_manager.cpp
+++ b/editor/editor_dock_manager.cpp
@@ -277,7 +277,7 @@ void EditorDockManager::_restore_dock_to_saved_window(Control *p_dock, const Dic
p_window_dump.get("window_screen_rect", Rect2i()));
}
-void EditorDockManager::_dock_move_to_bottom(Control *p_dock) {
+void EditorDockManager::_dock_move_to_bottom(Control *p_dock, bool p_visible) {
_move_dock(p_dock, nullptr);
all_docks[p_dock].at_bottom = true;
@@ -288,7 +288,7 @@ void EditorDockManager::_dock_move_to_bottom(Control *p_dock) {
// Force docks moved to the bottom to appear first in the list, and give them their associated shortcut to toggle their bottom panel.
Button *bottom_button = EditorNode::get_bottom_panel()->add_item(all_docks[p_dock].title, p_dock, all_docks[p_dock].shortcut, true);
bottom_button->connect(SceneStringName(gui_input), callable_mp(this, &EditorDockManager::_bottom_dock_button_gui_input).bind(bottom_button).bind(p_dock));
- EditorNode::get_bottom_panel()->make_item_visible(p_dock);
+ EditorNode::get_bottom_panel()->make_item_visible(p_dock, p_visible);
}
void EditorDockManager::_dock_remove_from_bottom(Control *p_dock) {
@@ -548,11 +548,13 @@ void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const S
// Don't open disabled docks.
continue;
}
+ bool at_bottom = false;
if (restore_window_on_load && floating_docks_dump.has(name)) {
all_docks[dock].previous_at_bottom = dock_bottom.has(name);
_restore_dock_to_saved_window(dock, floating_docks_dump[name]);
} else if (dock_bottom.has(name)) {
- _dock_move_to_bottom(dock);
+ _dock_move_to_bottom(dock, false);
+ at_bottom = true;
} else if (i >= 0) {
_move_dock(dock, dock_slot[i], 0);
}
@@ -564,7 +566,11 @@ void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const S
} else {
// Make sure it is open.
all_docks[dock].open = true;
- dock->show();
+ // It's important to not update the visibility of bottom panels.
+ // Visibility of bottom panels are managed in EditorBottomPanel.
+ if (!at_bottom) {
+ dock->show();
+ }
}
all_docks[dock].dock_slot_index = i;
@@ -668,7 +674,7 @@ void EditorDockManager::open_dock(Control *p_dock, bool p_set_current) {
// Open dock to its previous location.
if (all_docks[p_dock].previous_at_bottom) {
- _dock_move_to_bottom(p_dock);
+ _dock_move_to_bottom(p_dock, true);
} else if (all_docks[p_dock].dock_slot_index != DOCK_SLOT_NONE) {
TabContainer *slot = dock_slot[all_docks[p_dock].dock_slot_index];
int tab_index = all_docks[p_dock].previous_tab_index;
@@ -899,7 +905,7 @@ void DockContextPopup::_float_dock() {
void DockContextPopup::_move_dock_to_bottom() {
hide();
- dock_manager->_dock_move_to_bottom(context_dock);
+ dock_manager->_dock_move_to_bottom(context_dock, true);
dock_manager->_update_layout();
}
diff --git a/editor/editor_dock_manager.h b/editor/editor_dock_manager.h
index 226222c55a..1e6b413d14 100644
--- a/editor/editor_dock_manager.h
+++ b/editor/editor_dock_manager.h
@@ -121,7 +121,7 @@ private:
void _open_dock_in_window(Control *p_dock, bool p_show_window = true, bool p_reset_size = false);
void _restore_dock_to_saved_window(Control *p_dock, const Dictionary &p_window_dump);
- void _dock_move_to_bottom(Control *p_dock);
+ void _dock_move_to_bottom(Control *p_dock, bool p_visible);
void _dock_remove_from_bottom(Control *p_dock);
bool _is_dock_at_bottom(Control *p_dock);
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index f0dc850af0..4664defa59 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -150,6 +150,11 @@ uint64_t EditorFileSystemDirectory::get_file_modified_time(int p_idx) const {
return files[p_idx]->modified_time;
}
+uint64_t EditorFileSystemDirectory::get_file_import_modified_time(int p_idx) const {
+ ERR_FAIL_INDEX_V(p_idx, files.size(), 0);
+ return files[p_idx]->import_modified_time;
+}
+
String EditorFileSystemDirectory::get_file_script_class_name(int p_idx) const {
return files[p_idx]->script_class_name;
}
@@ -326,8 +331,8 @@ void EditorFileSystem::_scan_filesystem() {
FileCache fc;
fc.type = split[1];
if (fc.type.contains("/")) {
- fc.type = fc.type.get_slice("/", 0);
- fc.resource_script_class = fc.type.get_slice("/", 1);
+ fc.type = split[1].get_slice("/", 0);
+ fc.resource_script_class = split[1].get_slice("/", 1);
}
fc.uid = split[2].to_int();
fc.modification_time = split[3].to_int();
@@ -720,12 +725,22 @@ bool EditorFileSystem::_update_scan_actions() {
int idx = ia.dir->find_file_index(ia.file);
ERR_CONTINUE(idx == -1);
String full_path = ia.dir->get_file_path(idx);
- if (_test_for_reimport(full_path, false)) {
+
+ bool need_reimport = _test_for_reimport(full_path, false);
+ if (!need_reimport && FileAccess::exists(full_path + ".import")) {
+ uint64_t import_mt = ia.dir->get_file_import_modified_time(idx);
+ if (import_mt != FileAccess::get_modified_time(full_path + ".import")) {
+ need_reimport = true;
+ }
+ }
+
+ if (need_reimport) {
//must reimport
reimports.push_back(full_path);
Vector<String> dependencies = _get_dependencies(full_path);
- for (const String &dependency_path : dependencies) {
- if (import_extensions.has(dependency_path.get_extension())) {
+ for (const String &dep : dependencies) {
+ const String &dependency_path = dep.contains("::") ? dep.get_slice("::", 0) : dep;
+ if (import_extensions.has(dep.get_extension())) {
reimports.push_back(dependency_path);
}
}
@@ -1748,7 +1763,8 @@ String EditorFileSystem::_get_global_script_class(const String &p_type, const St
void EditorFileSystem::_update_file_icon_path(EditorFileSystemDirectory::FileInfo *file_info) {
String icon_path;
if (file_info->script_class_icon_path.is_empty() && !file_info->deps.is_empty()) {
- const String &script_path = file_info->deps[0]; // Assuming the first dependency is a script.
+ const String &script_dep = file_info->deps[0]; // Assuming the first dependency is a script.
+ const String &script_path = script_dep.contains("::") ? script_dep.get_slice("::", 2) : script_dep;
if (!script_path.is_empty()) {
String *cached = file_icon_cache.getptr(script_path);
if (cached) {
@@ -1891,7 +1907,7 @@ void EditorFileSystem::_update_scene_groups() {
continue;
}
- const HashSet<StringName> scene_groups = _get_scene_groups(path);
+ 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);
}
@@ -1935,12 +1951,6 @@ void EditorFileSystem::_get_all_scenes(EditorFileSystemDirectory *p_dir, HashSet
}
}
-HashSet<StringName> EditorFileSystem::_get_scene_groups(const String &p_path) {
- Ref<PackedScene> packed_scene = ResourceLoader::load(p_path);
- ERR_FAIL_COND_V(packed_scene.is_null(), HashSet<StringName>());
- return packed_scene->get_state()->get_all_groups();
-}
-
void EditorFileSystem::update_file(const String &p_file) {
ERR_FAIL_COND(p_file.is_empty());
update_files({ p_file });
@@ -2072,7 +2082,6 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) {
}
if (updated) {
- _process_update_pending();
if (update_files_icon_cache) {
_update_files_icon_path();
} else {
@@ -2080,7 +2089,10 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) {
_update_file_icon_path(fi);
}
}
- call_deferred(SNAME("emit_signal"), "filesystem_changed"); //update later
+ if (!is_scanning()) {
+ _process_update_pending();
+ call_deferred(SNAME("emit_signal"), "filesystem_changed"); //update later
+ }
}
}
diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h
index b0c6f0de51..1bc24416eb 100644
--- a/editor/editor_file_system.h
+++ b/editor/editor_file_system.h
@@ -89,6 +89,7 @@ public:
Vector<String> get_file_deps(int p_idx) const;
bool get_file_import_is_valid(int p_idx) const;
uint64_t get_file_modified_time(int p_idx) const;
+ uint64_t get_file_import_modified_time(int p_idx) const;
String get_file_script_class_name(int p_idx) const; //used for scripts
String get_file_script_class_extends(int p_idx) const; //used for scripts
String get_file_script_class_icon_path(int p_idx) const; //used for scripts
@@ -290,7 +291,6 @@ class EditorFileSystem : public Node {
void _queue_update_scene_groups(const String &p_path);
void _update_scene_groups();
void _update_pending_scene_groups();
- HashSet<StringName> _get_scene_groups(const String &p_path);
void _get_all_scenes(EditorFileSystemDirectory *p_dir, HashSet<String> &r_list);
String _get_global_script_class(const String &p_type, const String &p_path, String *r_extends, String *r_icon_path) const;
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index 00ac1c7c6f..5725129f65 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -3761,6 +3761,12 @@ EditorHelpBit::EditorHelpBit(const String &p_symbol) {
/// EditorHelpBitTooltip ///
+void EditorHelpBitTooltip::_start_timer() {
+ if (timer->is_inside_tree() && timer->is_stopped()) {
+ timer->start();
+ }
+}
+
void EditorHelpBitTooltip::_safe_queue_free() {
if (_pushing_input > 0) {
_need_free = true;
@@ -3769,13 +3775,20 @@ void EditorHelpBitTooltip::_safe_queue_free() {
}
}
+void EditorHelpBitTooltip::_target_gui_input(const Ref<InputEvent> &p_event) {
+ const Ref<InputEventMouse> mouse_event = p_event;
+ if (mouse_event.is_valid()) {
+ _start_timer();
+ }
+}
+
void EditorHelpBitTooltip::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_WM_MOUSE_ENTER:
timer->stop();
break;
case NOTIFICATION_WM_MOUSE_EXIT:
- timer->start();
+ _start_timer();
break;
}
}
@@ -3783,7 +3796,7 @@ void EditorHelpBitTooltip::_notification(int p_what) {
// Forwards non-mouse input to the parent viewport.
void EditorHelpBitTooltip::_input_from_window(const Ref<InputEvent> &p_event) {
if (p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) {
- hide(); // Will be deleted on its timer.
+ _safe_queue_free();
} else {
const Ref<InputEventMouse> mouse_event = p_event;
if (mouse_event.is_null()) {
@@ -3801,7 +3814,7 @@ void EditorHelpBitTooltip::_input_from_window(const Ref<InputEvent> &p_event) {
void EditorHelpBitTooltip::show_tooltip(EditorHelpBit *p_help_bit, Control *p_target) {
ERR_FAIL_NULL(p_help_bit);
EditorHelpBitTooltip *tooltip = memnew(EditorHelpBitTooltip(p_target));
- p_help_bit->connect("request_hide", callable_mp(static_cast<Window *>(tooltip), &Window::hide)); // Will be deleted on its timer.
+ p_help_bit->connect("request_hide", callable_mp(tooltip, &EditorHelpBitTooltip::_safe_queue_free));
tooltip->add_child(p_help_bit);
p_target->get_viewport()->add_child(tooltip);
p_help_bit->update_content_height();
@@ -3858,8 +3871,8 @@ EditorHelpBitTooltip::EditorHelpBitTooltip(Control *p_target) {
add_child(timer);
ERR_FAIL_NULL(p_target);
- p_target->connect(SceneStringName(mouse_entered), callable_mp(timer, &Timer::stop));
- p_target->connect(SceneStringName(mouse_exited), callable_mp(timer, &Timer::start).bind(-1));
+ p_target->connect(SceneStringName(mouse_exited), callable_mp(this, &EditorHelpBitTooltip::_start_timer));
+ p_target->connect(SceneStringName(gui_input), callable_mp(this, &EditorHelpBitTooltip::_target_gui_input));
}
#if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED)
diff --git a/editor/editor_help.h b/editor/editor_help.h
index 8d1fec713e..93f74cb2c1 100644
--- a/editor/editor_help.h
+++ b/editor/editor_help.h
@@ -329,7 +329,9 @@ class EditorHelpBitTooltip : public PopupPanel {
int _pushing_input = 0;
bool _need_free = false;
+ void _start_timer();
void _safe_queue_free();
+ void _target_gui_input(const Ref<InputEvent> &p_event);
protected:
void _notification(int p_what);
diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp
index 6a016c217a..0dfbcd0e0d 100644
--- a/editor/editor_log.cpp
+++ b/editor/editor_log.cpp
@@ -58,8 +58,8 @@ void EditorLog::_error_handler(void *p_self, const char *p_func, const char *p_f
MessageType message_type = p_type == ERR_HANDLER_WARNING ? MSG_TYPE_WARNING : MSG_TYPE_ERROR;
- if (self->current != Thread::get_caller_id()) {
- callable_mp(self, &EditorLog::add_message).call_deferred(err_str, message_type);
+ if (!Thread::is_main_thread()) {
+ MessageQueue::get_main_singleton()->push_callable(callable_mp(self, &EditorLog::add_message), err_str, message_type);
} else {
self->add_message(err_str, message_type);
}
@@ -557,8 +557,6 @@ EditorLog::EditorLog() {
eh.errfunc = _error_handler;
eh.userdata = this;
add_error_handler(&eh);
-
- current = Thread::get_caller_id();
}
void EditorLog::deinit() {
diff --git a/editor/editor_log.h b/editor/editor_log.h
index 7012a2a43c..9c652e912a 100644
--- a/editor/editor_log.h
+++ b/editor/editor_log.h
@@ -156,8 +156,6 @@ private:
ErrorHandlerList eh;
- Thread::ID current;
-
//void _dragged(const Point2& p_ofs);
void _meta_clicked(const String &p_meta);
void _clear_request();
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 71603e6190..fd49920c6b 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -317,14 +317,10 @@ void EditorNode::shortcut_input(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> k = p_event;
if ((k.is_valid() && k->is_pressed() && !k->is_echo()) || Object::cast_to<InputEventShortcut>(*p_event)) {
- EditorPlugin *old_editor = editor_plugin_screen;
-
+ bool is_handled = true;
if (ED_IS_SHORTCUT("editor/filter_files", p_event)) {
FileSystemDock::get_singleton()->focus_on_filter();
- get_tree()->get_root()->set_input_as_handled();
- }
-
- if (ED_IS_SHORTCUT("editor/editor_2d", p_event)) {
+ } else if (ED_IS_SHORTCUT("editor/editor_2d", p_event)) {
editor_select(EDITOR_2D);
} else if (ED_IS_SHORTCUT("editor/editor_3d", p_event)) {
editor_select(EDITOR_3D);
@@ -343,9 +339,10 @@ void EditorNode::shortcut_input(const Ref<InputEvent> &p_event) {
} else if (ED_IS_SHORTCUT("editor/toggle_last_opened_bottom_panel", p_event)) {
bottom_panel->toggle_last_opened_bottom_panel();
} else {
+ is_handled = false;
}
- if (old_editor != editor_plugin_screen) {
+ if (is_handled) {
get_tree()->get_root()->set_input_as_handled();
}
}
@@ -5261,6 +5258,14 @@ bool EditorNode::has_scenes_in_session() {
return !scenes.is_empty();
}
+void EditorNode::undo() {
+ trigger_menu_option(EDIT_UNDO, true);
+}
+
+void EditorNode::redo() {
+ trigger_menu_option(EDIT_REDO, true);
+}
+
bool EditorNode::ensure_main_scene(bool p_from_native) {
pick_main_scene->set_meta("from_native", p_from_native); // Whether from play button or native run.
String main_scene = GLOBAL_GET("application/run/main_scene");
@@ -5973,9 +5978,6 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins
is_editable = owner->is_editable_instance(original_node);
}
- // For clear instance state for path recaching.
- instantiated_node->set_scene_instance_state(Ref<SceneState>());
-
bool original_node_is_displayed_folded = original_node->is_displayed_folded();
bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder();
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 28bd692ddf..7a26156ab8 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -916,6 +916,9 @@ public:
bool has_scenes_in_session();
+ void undo();
+ void redo();
+
int execute_and_show_output(const String &p_title, const String &p_path, const List<String> &p_arguments, bool p_close_on_ok = true, bool p_close_on_errors = false, String *r_output = nullptr);
EditorNode();
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index e021be9668..5d3cc80da9 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -474,6 +474,11 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/vsync_mode", 1, "Disabled,Enabled,Adaptive,Mailbox")
EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/update_continuously", false, "")
+#ifdef ANDROID_ENABLED
+ EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/android/use_accumulated_input", true, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
+ EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/android/use_input_buffering", true, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
+#endif
+
// Inspector
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "interface/inspector/max_array_dictionary_items_per_page", 20, "10,100,1")
EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/inspector/show_low_level_opentype_features", false, "")
@@ -781,6 +786,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
// Animation
_initial_set("editors/animation/autorename_animation_tracks", true);
+ _initial_set("editors/animation/confirm_insert_track", true);
_initial_set("editors/animation/default_create_bezier_tracks", false);
_initial_set("editors/animation/default_create_reset_tracks", true);
_initial_set("editors/animation/onion_layers_past_color", Color(1, 0, 0));
diff --git a/editor/editor_settings_dialog.cpp b/editor/editor_settings_dialog.cpp
index 6fd6a7103f..a71d43ad51 100644
--- a/editor/editor_settings_dialog.cpp
+++ b/editor/editor_settings_dialog.cpp
@@ -168,28 +168,17 @@ void EditorSettingsDialog::_notification(int p_what) {
}
void EditorSettingsDialog::shortcut_input(const Ref<InputEvent> &p_event) {
- ERR_FAIL_COND(p_event.is_null());
- EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
-
const Ref<InputEventKey> k = p_event;
if (k.is_valid() && k->is_pressed()) {
bool handled = false;
if (ED_IS_SHORTCUT("ui_undo", p_event)) {
- String action = undo_redo->get_current_action_name();
- if (!action.is_empty()) {
- EditorNode::get_log()->add_message(vformat(TTR("Undo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
- }
- undo_redo->undo();
+ EditorNode::get_singleton()->undo();
handled = true;
}
if (ED_IS_SHORTCUT("ui_redo", p_event)) {
- undo_redo->redo();
- String action = undo_redo->get_current_action_name();
- if (!action.is_empty()) {
- EditorNode::get_log()->add_message(vformat(TTR("Redo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
- }
+ EditorNode::get_singleton()->redo();
handled = true;
}
diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp
index 9a0f2f18fa..72ab186036 100644
--- a/editor/export/editor_export.cpp
+++ b/editor/export/editor_export.cpp
@@ -423,8 +423,8 @@ EditorExport::EditorExport() {
save_timer->set_one_shot(true);
save_timer->connect("timeout", callable_mp(this, &EditorExport::_save));
- _export_presets_updated = "export_presets_updated";
- _export_presets_runnable_updated = "export_presets_runnable_updated";
+ _export_presets_updated = StringName("export_presets_updated", true);
+ _export_presets_runnable_updated = StringName("export_presets_runnable_updated", true);
singleton = this;
set_process(true);
diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp
index 527544fea3..c0646dc572 100644
--- a/editor/export/editor_export_platform.cpp
+++ b/editor/export/editor_export_platform.cpp
@@ -797,10 +797,6 @@ String EditorExportPlatform::_export_customize(const String &p_path, LocalVector
if (!customize_scenes_plugins.is_empty()) {
for (Ref<EditorExportPlugin> &plugin : customize_scenes_plugins) {
Node *customized = plugin->_customize_scene(node, p_path);
- if (plugin->skipped) {
- plugin->_clear();
- return String();
- }
if (customized != nullptr) {
node = customized;
modified = true;
@@ -834,10 +830,6 @@ String EditorExportPlatform::_export_customize(const String &p_path, LocalVector
if (!customize_resources_plugins.is_empty()) {
for (Ref<EditorExportPlugin> &plugin : customize_resources_plugins) {
Ref<Resource> new_res = plugin->_customize_resource(res, p_path);
- if (plugin->skipped) {
- plugin->_clear();
- return String();
- }
if (new_res.is_valid()) {
modified = true;
if (new_res != res) {
@@ -1132,33 +1124,97 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
}
//store everything in the export medium
- int idx = 0;
int total = paths.size();
+ // idx is incremented at the beginning of the paths loop to easily allow
+ // for continue statements without accidentally skipping an increment.
+ int idx = total > 0 ? -1 : 0;
for (const String &E : paths) {
+ idx++;
String path = E;
String type = ResourceLoader::get_resource_type(path);
- if (FileAccess::exists(path + ".import")) {
- // Before doing this, try to see if it can be customized.
+ bool has_import_file = FileAccess::exists(path + ".import");
+ Ref<ConfigFile> config;
+ if (has_import_file) {
+ config.instantiate();
+ err = config->load(path + ".import");
+ if (err != OK) {
+ ERR_PRINT("Could not parse: '" + path + "', not exported.");
+ continue;
+ }
+
+ String importer_type = config->get_value("remap", "importer");
+
+ if (importer_type == "skip") {
+ // Skip file.
+ continue;
+ }
+ }
+
+ bool do_export = true;
+ for (int i = 0; i < export_plugins.size(); i++) {
+ if (GDVIRTUAL_IS_OVERRIDDEN_PTR(export_plugins[i], _export_file)) {
+ export_plugins.write[i]->_export_file_script(path, type, features_psa);
+ } else {
+ export_plugins.write[i]->_export_file(path, type, features);
+ }
+ if (p_so_func) {
+ for (int j = 0; j < export_plugins[i]->shared_objects.size(); j++) {
+ err = p_so_func(p_udata, export_plugins[i]->shared_objects[j]);
+ if (err != OK) {
+ return err;
+ }
+ }
+ }
+
+ for (int j = 0; j < export_plugins[i]->extra_files.size(); j++) {
+ err = p_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, idx, total, enc_in_filters, enc_ex_filters, key);
+ if (err != OK) {
+ return err;
+ }
+ if (export_plugins[i]->extra_files[j].remap) {
+ do_export = false; // If remap, do not.
+ path_remaps.push_back(path);
+ path_remaps.push_back(export_plugins[i]->extra_files[j].path);
+ }
+ }
+
+ if (export_plugins[i]->skipped) {
+ do_export = false;
+ }
+ export_plugins.write[i]->_clear();
+
+ if (!do_export) {
+ break;
+ }
+ }
+ if (!do_export) {
+ continue;
+ }
+
+ if (has_import_file) {
+ String importer_type = config->get_value("remap", "importer");
+
+ if (importer_type == "keep") {
+ // Just keep file as-is.
+ Vector<uint8_t> array = FileAccess::get_file_as_bytes(path);
+ err = p_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key);
+
+ if (err != OK) {
+ return err;
+ }
- String export_path = _export_customize(path, customize_resources_plugins, customize_scenes_plugins, export_cache, export_base_path, false);
- if (export_path.is_empty()) {
- // Skipped from plugin.
continue;
}
+ // Before doing this, try to see if it can be customized.
+ String export_path = _export_customize(path, customize_resources_plugins, customize_scenes_plugins, export_cache, export_base_path, false);
+
if (export_path != path) {
// It was actually customized.
// Since the original file is likely not recognized, just use the import system.
- Ref<ConfigFile> config;
- config.instantiate();
- err = config->load(path + ".import");
- if (err != OK) {
- ERR_PRINT("Could not parse: '" + path + "', not exported.");
- continue;
- }
config->set_value("remap", "type", ResourceLoader::get_resource_type(export_path));
// Erase all Paths.
@@ -1194,33 +1250,6 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
}
} else {
// File is imported and not customized, replace by what it imports.
- Ref<ConfigFile> config;
- config.instantiate();
- err = config->load(path + ".import");
- if (err != OK) {
- ERR_PRINT("Could not parse: '" + path + "', not exported.");
- continue;
- }
-
- String importer_type = config->get_value("remap", "importer");
-
- if (importer_type == "skip") {
- // Skip file.
- continue;
- }
-
- if (importer_type == "keep") {
- // Just keep file as-is.
- Vector<uint8_t> array = FileAccess::get_file_as_bytes(path);
- err = p_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key);
-
- if (err != OK) {
- return err;
- }
-
- continue;
- }
-
List<String> remaps;
config->get_section_keys("remap", &remaps);
@@ -1282,66 +1311,24 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
}
} else {
- // Customize.
+ // Just store it as it comes.
- bool do_export = true;
- for (int i = 0; i < export_plugins.size(); i++) {
- if (GDVIRTUAL_IS_OVERRIDDEN_PTR(export_plugins[i], _export_file)) {
- export_plugins.write[i]->_export_file_script(path, type, features_psa);
- } else {
- export_plugins.write[i]->_export_file(path, type, features);
- }
- if (p_so_func) {
- for (int j = 0; j < export_plugins[i]->shared_objects.size(); j++) {
- err = p_so_func(p_udata, export_plugins[i]->shared_objects[j]);
- if (err != OK) {
- return err;
- }
- }
- }
-
- for (int j = 0; j < export_plugins[i]->extra_files.size(); j++) {
- err = p_func(p_udata, export_plugins[i]->extra_files[j].path, export_plugins[i]->extra_files[j].data, idx, total, enc_in_filters, enc_ex_filters, key);
- if (err != OK) {
- return err;
- }
- if (export_plugins[i]->extra_files[j].remap) {
- do_export = false; //if remap, do not
- path_remaps.push_back(path);
- path_remaps.push_back(export_plugins[i]->extra_files[j].path);
- }
- }
+ // Customization only happens if plugins did not take care of it before.
+ bool force_binary = convert_text_to_binary && (path.get_extension().to_lower() == "tres" || path.get_extension().to_lower() == "tscn");
+ String export_path = _export_customize(path, customize_resources_plugins, customize_scenes_plugins, export_cache, export_base_path, force_binary);
- if (export_plugins[i]->skipped) {
- do_export = false;
- }
- export_plugins.write[i]->_clear();
-
- if (!do_export) {
- break; //apologies, not exporting
- }
+ if (export_path != path) {
+ // Add a remap entry.
+ path_remaps.push_back(path);
+ path_remaps.push_back(export_path);
}
- //just store it as it comes
- if (do_export) {
- // Customization only happens if plugins did not take care of it before
- bool force_binary = convert_text_to_binary && (path.get_extension().to_lower() == "tres" || path.get_extension().to_lower() == "tscn");
- String export_path = _export_customize(path, customize_resources_plugins, customize_scenes_plugins, export_cache, export_base_path, force_binary);
-
- if (export_path != path) {
- // Add a remap entry
- path_remaps.push_back(path);
- path_remaps.push_back(export_path);
- }
- Vector<uint8_t> array = FileAccess::get_file_as_bytes(export_path);
- err = p_func(p_udata, export_path, array, idx, total, enc_in_filters, enc_ex_filters, key);
- if (err != OK) {
- return err;
- }
+ Vector<uint8_t> array = FileAccess::get_file_as_bytes(export_path);
+ err = p_func(p_udata, export_path, array, idx, total, enc_in_filters, enc_ex_filters, key);
+ if (err != OK) {
+ return err;
}
}
-
- idx++;
}
if (convert_text_to_binary || !customize_resources_plugins.is_empty() || !customize_scenes_plugins.is_empty()) {
diff --git a/editor/gui/editor_bottom_panel.cpp b/editor/gui/editor_bottom_panel.cpp
index f2c4a13e05..3e74a3c94e 100644
--- a/editor/gui/editor_bottom_panel.cpp
+++ b/editor/gui/editor_bottom_panel.cpp
@@ -178,7 +178,11 @@ Button *EditorBottomPanel::add_item(String p_text, Control *p_item, const Ref<Sh
bpi.button = tb;
bpi.control = p_item;
bpi.name = p_text;
- items.push_back(bpi);
+ if (p_at_front) {
+ items.insert(0, bpi);
+ } else {
+ items.push_back(bpi);
+ }
return tb;
}
diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp
index 3e94310c83..2b2ce42b9b 100644
--- a/editor/gui/editor_file_dialog.cpp
+++ b/editor/gui/editor_file_dialog.cpp
@@ -1968,6 +1968,7 @@ void EditorFileDialog::_bind_methods() {
base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults.name, &EditorFileDialog::set_option_name, &EditorFileDialog::get_option_name);
base_property_helper.register_property(PropertyInfo(Variant::PACKED_STRING_ARRAY, "values"), defaults.values, &EditorFileDialog::set_option_values, &EditorFileDialog::get_option_values);
base_property_helper.register_property(PropertyInfo(Variant::INT, "default"), defaults.default_idx, &EditorFileDialog::set_option_default, &EditorFileDialog::get_option_default);
+ PropertyListHelper::register_base_helper(&base_property_helper);
}
void EditorFileDialog::set_show_hidden_files(bool p_show) {
diff --git a/editor/import/3d/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp
index 6d68e93c75..a0c05598a2 100644
--- a/editor/import/3d/resource_importer_obj.cpp
+++ b/editor/import/3d/resource_importer_obj.cpp
@@ -425,9 +425,13 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes,
}
if (!current_material.is_empty()) {
- mesh->set_surface_name(mesh->get_surface_count() - 1, current_material.get_basename());
+ if (mesh->get_surface_count() >= 1) {
+ mesh->set_surface_name(mesh->get_surface_count() - 1, current_material.get_basename());
+ }
} else if (!current_group.is_empty()) {
- mesh->set_surface_name(mesh->get_surface_count() - 1, current_group);
+ if (mesh->get_surface_count() >= 1) {
+ mesh->set_surface_name(mesh->get_surface_count() - 1, current_group);
+ }
}
Array array = surf_tool->commit_to_arrays();
diff --git a/editor/plugins/animation_library_editor.cpp b/editor/plugins/animation_library_editor.cpp
index afe7ea83d8..b07db993ba 100644
--- a/editor/plugins/animation_library_editor.cpp
+++ b/editor/plugins/animation_library_editor.cpp
@@ -781,6 +781,27 @@ void AnimationLibraryEditor::_update_editor(Object *p_mixer) {
emit_signal("update_editor", p_mixer);
}
+void AnimationLibraryEditor::shortcut_input(const Ref<InputEvent> &p_event) {
+ const Ref<InputEventKey> k = p_event;
+ if (k.is_valid() && k->is_pressed()) {
+ bool handled = false;
+
+ if (ED_IS_SHORTCUT("ui_undo", p_event)) {
+ EditorNode::get_singleton()->undo();
+ handled = true;
+ }
+
+ if (ED_IS_SHORTCUT("ui_redo", p_event)) {
+ EditorNode::get_singleton()->redo();
+ handled = true;
+ }
+
+ if (handled) {
+ set_input_as_handled();
+ }
+ }
+}
+
void AnimationLibraryEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_editor", "mixer"), &AnimationLibraryEditor::_update_editor);
ADD_SIGNAL(MethodInfo("update_editor"));
@@ -788,6 +809,7 @@ void AnimationLibraryEditor::_bind_methods() {
AnimationLibraryEditor::AnimationLibraryEditor() {
set_title(TTR("Edit Animation Libraries"));
+ set_process_shortcut_input(true);
file_dialog = memnew(EditorFileDialog);
add_child(file_dialog);
diff --git a/editor/plugins/animation_library_editor.h b/editor/plugins/animation_library_editor.h
index c8d9274f4f..beb34c6343 100644
--- a/editor/plugins/animation_library_editor.h
+++ b/editor/plugins/animation_library_editor.h
@@ -113,6 +113,7 @@ class AnimationLibraryEditor : public AcceptDialog {
protected:
void _notification(int p_what);
void _update_editor(Object *p_mixer);
+ virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
static void _bind_methods();
public:
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 484d2b1fff..02fa582da4 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -1395,23 +1395,14 @@ void AnimationPlayerEditor::_current_animation_changed(const String &p_name) {
void AnimationPlayerEditor::_animation_key_editor_anim_len_changed(float p_len) {
frame->set_max(p_len);
}
-
-void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_timeline_only) {
+void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_timeline_only, bool p_update_position_only) {
timeline_position = p_pos;
- if (!is_visible_in_tree()) {
- return;
- }
-
- if (!player) {
- return;
- }
-
- if (player->is_playing()) {
- return;
- }
-
- if (!player->has_animation(player->get_assigned_animation())) {
+ if (!is_visible_in_tree() ||
+ p_update_position_only ||
+ !player ||
+ player->is_playing() ||
+ !player->has_animation(player->get_assigned_animation())) {
return;
}
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index e624522566..4a3b1f37ab 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -214,7 +214,7 @@ class AnimationPlayerEditor : public VBoxContainer {
void _animation_player_changed(Object *p_pl);
void _animation_libraries_updated();
- void _animation_key_editor_seek(float p_pos, bool p_timeline_only = false);
+ 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);
virtual void shortcut_input(const Ref<InputEvent> &p_ev) override;
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 4e2940a8cb..f9be1b08d9 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -772,7 +772,6 @@ bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_po
// Reselect
if (Engine::get_singleton()->is_editor_hint()) {
selected_from_canvas = true;
- EditorNode::get_singleton()->edit_node(item);
}
}
}
@@ -5199,7 +5198,7 @@ CanvasItemEditor::CanvasItemEditor() {
SceneTreeDock::get_singleton()->connect("node_created", callable_mp(this, &CanvasItemEditor::_adjust_new_node_position));
SceneTreeDock::get_singleton()->connect("add_node_used", callable_mp(this, &CanvasItemEditor::_reset_create_position));
- // Add some margin to the sides for better esthetics.
+ // Add some margin to the sides for better aesthetics.
// This prevents the first button's hover/pressed effect from "touching" the panel's border,
// which looks ugly.
MarginContainer *toolbar_margin = memnew(MarginContainer);
diff --git a/editor/plugins/font_config_plugin.cpp b/editor/plugins/font_config_plugin.cpp
index 15b268337f..e6ce63fe36 100644
--- a/editor/plugins/font_config_plugin.cpp
+++ b/editor/plugins/font_config_plugin.cpp
@@ -386,15 +386,8 @@ EditorPropertyFontMetaOverride::EditorPropertyFontMetaOverride(bool p_script) {
void EditorPropertyOTVariation::_property_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) {
if (p_property.begins_with("keys")) {
Dictionary dict = object->get_dict();
- Dictionary defaults_dict = object->get_defaults();
int key = p_property.get_slice("/", 1).to_int();
dict[key] = (int)p_value;
- if (defaults_dict.has(key)) {
- Vector3i range = defaults_dict[key];
- if (range.z == (int)p_value) {
- dict.erase(key);
- }
- }
emit_changed(get_edited_property(), dict, "", true);
@@ -422,6 +415,14 @@ void EditorPropertyOTVariation::update_property() {
Dictionary supported = (fd.is_valid()) ? fd->get_supported_variation_list() : Dictionary();
+ for (int i = 0; i < supported.size(); i++) {
+ int name_tag = supported.get_key_at_index(i);
+ Vector3i range = supported.get_value_at_index(i);
+ if ((dict.has(name_tag) && dict[name_tag].get_type() == Variant::NIL) || !dict.has(name_tag)) {
+ dict[name_tag] = range.z;
+ }
+ }
+
edit->set_text(vformat(TTR("Variation Coordinates (%d)"), supported.size()));
bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
@@ -481,7 +482,21 @@ void EditorPropertyOTVariation::update_property() {
prop->set_object_and_property(object.ptr(), "keys/" + itos(name_tag));
String name = TS->tag_to_name(name_tag);
- prop->set_label(name.capitalize());
+ String name_cap;
+ {
+ String aux = name.replace("_", " ").strip_edges();
+ for (int j = 0; j < aux.get_slice_count(" "); j++) {
+ String slice = aux.get_slicec(' ', j);
+ if (slice.length() > 0) {
+ slice[0] = String::char_uppercase(slice[0]);
+ if (i > 0) {
+ name_cap += " ";
+ }
+ name_cap += slice;
+ }
+ }
+ }
+ prop->set_label(name_cap);
prop->set_tooltip_text(name);
prop->set_selectable(false);
@@ -935,6 +950,12 @@ void FontPreview::_notification(int p_what) {
font->draw_string(get_canvas_item(), Point2(0, font->get_height(font_size) + 2 * EDSCALE), TTR("Unable to preview font"), HORIZONTAL_ALIGNMENT_CENTER, get_size().x, font_size, text_color);
}
} break;
+
+ case NOTIFICATION_EXIT_TREE: {
+ if (prev_font.is_valid()) {
+ prev_font->disconnect_changed(callable_mp(this, &FontPreview::_preview_changed));
+ }
+ } break;
}
}
@@ -945,7 +966,17 @@ Size2 FontPreview::get_minimum_size() const {
}
void FontPreview::set_data(const Ref<Font> &p_f) {
+ if (prev_font.is_valid()) {
+ prev_font->disconnect_changed(callable_mp(this, &FontPreview::_preview_changed));
+ }
prev_font = p_f;
+ if (prev_font.is_valid()) {
+ prev_font->connect_changed(callable_mp(this, &FontPreview::_preview_changed));
+ }
+ queue_redraw();
+}
+
+void FontPreview::_preview_changed() {
queue_redraw();
}
diff --git a/editor/plugins/font_config_plugin.h b/editor/plugins/font_config_plugin.h
index 7b2d26da4a..4e798fc3e8 100644
--- a/editor/plugins/font_config_plugin.h
+++ b/editor/plugins/font_config_plugin.h
@@ -225,6 +225,8 @@ protected:
Ref<Font> prev_font;
+ void _preview_changed();
+
public:
virtual Size2 get_minimum_size() const override;
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index c6a0dfb888..72eea8a27e 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -8474,7 +8474,7 @@ Node3DEditor::Node3DEditor() {
camera_override_viewport_id = 0;
- // Add some margin to the sides for better esthetics.
+ // Add some margin to the sides for better aesthetics.
// This prevents the first button's hover/pressed effect from "touching" the panel's border,
// which looks ugly.
MarginContainer *toolbar_margin = memnew(MarginContainer);
diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp
index b5db7bef70..e442c37edd 100644
--- a/editor/plugins/polygon_2d_editor_plugin.cpp
+++ b/editor/plugins/polygon_2d_editor_plugin.cpp
@@ -52,6 +52,31 @@
#include "scene/gui/texture_rect.h"
#include "scene/gui/view_panner.h"
+class UVEditDialog : public AcceptDialog {
+ GDCLASS(UVEditDialog, AcceptDialog);
+
+ void shortcut_input(const Ref<InputEvent> &p_event) override {
+ const Ref<InputEventKey> k = p_event;
+ if (k.is_valid() && k->is_pressed()) {
+ bool handled = false;
+
+ if (ED_IS_SHORTCUT("ui_undo", p_event)) {
+ EditorNode::get_singleton()->undo();
+ handled = true;
+ }
+
+ if (ED_IS_SHORTCUT("ui_redo", p_event)) {
+ EditorNode::get_singleton()->redo();
+ handled = true;
+ }
+
+ if (handled) {
+ set_input_as_handled();
+ }
+ }
+ }
+};
+
Node2D *Polygon2DEditor::_get_node() const {
return node;
}
@@ -1305,9 +1330,10 @@ Polygon2DEditor::Polygon2DEditor() {
button_uv->connect(SceneStringName(pressed), callable_mp(this, &Polygon2DEditor::_menu_option).bind(MODE_EDIT_UV));
uv_mode = UV_MODE_EDIT_POINT;
- uv_edit = memnew(AcceptDialog);
- add_child(uv_edit);
+ uv_edit = memnew(UVEditDialog);
uv_edit->set_title(TTR("Polygon 2D UV Editor"));
+ uv_edit->set_process_shortcut_input(true);
+ add_child(uv_edit);
uv_edit->connect(SceneStringName(confirmed), callable_mp(this, &Polygon2DEditor::_uv_edit_popup_hide));
uv_edit->connect("canceled", callable_mp(this, &Polygon2DEditor::_uv_edit_popup_hide));
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index a670c7937b..c51eb44aee 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -272,7 +272,7 @@ void ScriptEditorBase::_bind_methods() {
ADD_SIGNAL(MethodInfo("request_help", PropertyInfo(Variant::STRING, "topic")));
ADD_SIGNAL(MethodInfo("request_open_script_at_line", PropertyInfo(Variant::OBJECT, "script"), PropertyInfo(Variant::INT, "line")));
ADD_SIGNAL(MethodInfo("request_save_history"));
- ADD_SIGNAL(MethodInfo("request_save_previous_state", PropertyInfo(Variant::INT, "line")));
+ ADD_SIGNAL(MethodInfo("request_save_previous_state", PropertyInfo(Variant::DICTIONARY, "state")));
ADD_SIGNAL(MethodInfo("go_to_help", PropertyInfo(Variant::STRING, "what")));
ADD_SIGNAL(MethodInfo("search_in_files_requested", PropertyInfo(Variant::STRING, "text")));
ADD_SIGNAL(MethodInfo("replace_in_files_requested", PropertyInfo(Variant::STRING, "text")));
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 059e177874..96127ec93e 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -1819,15 +1819,25 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
CodeEdit *te = code_editor->get_text_editor();
Point2i pos = te->get_line_column_at_pos(p_point);
- int row = pos.y;
- int col = pos.x;
+ int drop_at_line = pos.y;
+ int drop_at_column = pos.x;
+ int selection_index = te->get_selection_at_line_column(drop_at_line, drop_at_column);
+
+ bool line_will_be_empty = false;
+ if (selection_index >= 0) {
+ // Dropped on a selection, it will be replaced.
+ drop_at_line = te->get_selection_from_line(selection_index);
+ drop_at_column = te->get_selection_from_column(selection_index);
+ line_will_be_empty = drop_at_column <= te->get_first_non_whitespace_column(drop_at_line) && te->get_selection_to_column(selection_index) == te->get_line(te->get_selection_to_line(selection_index)).length();
+ }
+
+ String text_to_drop;
const bool drop_modifier_pressed = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
- const String &line = te->get_line(row);
- const bool is_empty_line = line.is_empty() || te->get_first_non_whitespace_column(row) == line.length();
+ const String &line = te->get_line(drop_at_line);
+ const bool is_empty_line = line_will_be_empty || line.is_empty() || te->get_first_non_whitespace_column(drop_at_line) == line.length();
if (d.has("type") && String(d["type"]) == "resource") {
- te->remove_secondary_carets();
Ref<Resource> resource = d["resource"];
if (resource.is_null()) {
return;
@@ -1840,7 +1850,6 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
return;
}
- String text_to_drop;
if (drop_modifier_pressed) {
if (resource->is_built_in()) {
String warning = TTR("Preloading internal resources is not supported.");
@@ -1851,19 +1860,10 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
} else {
text_to_drop = _quote_drop_data(path);
}
-
- te->set_caret_line(row);
- te->set_caret_column(col);
- te->insert_text_at_caret(text_to_drop);
- te->grab_focus();
}
if (d.has("type") && (String(d["type"]) == "files" || String(d["type"]) == "files_and_dirs")) {
- te->remove_secondary_carets();
-
Array files = d["files"];
- String text_to_drop;
-
for (int i = 0; i < files.size(); i++) {
const String &path = String(files[i]);
@@ -1883,15 +1883,9 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
text_to_drop += is_empty_line ? "\n" : ", ";
}
}
-
- te->set_caret_line(row);
- te->set_caret_column(col);
- te->insert_text_at_caret(text_to_drop);
- te->grab_focus();
}
if (d.has("type") && String(d["type"]) == "nodes") {
- te->remove_secondary_carets();
Node *scene_root = get_tree()->get_edited_scene_root();
if (!scene_root) {
EditorNode::get_singleton()->show_warning(TTR("Can't drop nodes without an open scene."));
@@ -1909,7 +1903,6 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
}
Array nodes = d["nodes"];
- String text_to_drop;
if (drop_modifier_pressed) {
const bool use_type = EDITOR_GET("text_editor/completion/add_type_hints");
@@ -1981,27 +1974,33 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
text_to_drop += (is_unique ? "%" : "$") + path;
}
}
-
- te->set_caret_line(row);
- te->set_caret_column(col);
- te->insert_text_at_caret(text_to_drop);
- te->grab_focus();
}
if (d.has("type") && String(d["type"]) == "obj_property") {
- te->remove_secondary_carets();
-
bool add_literal = EDITOR_GET("text_editor/completion/add_node_path_literals");
- String text_to_drop = add_literal ? "^" : "";
+ text_to_drop = add_literal ? "^" : "";
// It is unclear whether properties may contain single or double quotes.
// Assume here that double-quotes may not exist. We are escaping single-quotes if necessary.
text_to_drop += _quote_drop_data(String(d["property"]));
+ }
+
+ if (text_to_drop.is_empty()) {
+ return;
+ }
- te->set_caret_line(row);
- te->set_caret_column(col);
- te->insert_text_at_caret(text_to_drop);
- te->grab_focus();
+ // Remove drag caret before any actions so it is not included in undo.
+ te->remove_drag_caret();
+ te->begin_complex_operation();
+ if (selection_index >= 0) {
+ te->delete_selection(selection_index);
}
+ te->remove_secondary_carets();
+ te->deselect();
+ te->set_caret_line(drop_at_line);
+ te->set_caret_column(drop_at_column);
+ te->insert_text_at_caret(text_to_drop);
+ te->end_complex_operation();
+ te->grab_focus();
}
void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp
index e8a7b3b514..f985bbc629 100644
--- a/editor/plugins/tiles/tile_data_editors.cpp
+++ b/editor/plugins/tiles/tile_data_editors.cpp
@@ -139,9 +139,17 @@ void GenericTilePolygonEditor::_base_control_draw() {
const Ref<StyleBox> focus_stylebox = get_theme_stylebox(SNAME("Focus"), EditorStringName(EditorStyles));
// Get the background data.
- TileData *tile_data = background_atlas_source->get_tile_data(background_atlas_coords, background_alternative_id);
- ERR_FAIL_NULL(tile_data);
- Rect2 background_region = background_atlas_source->get_tile_texture_region(background_atlas_coords);
+ Rect2 background_region;
+ TileData *tile_data = nullptr;
+
+ if (background_atlas_source.is_valid()) {
+ tile_data = background_atlas_source->get_tile_data(background_atlas_coords, background_alternative_id);
+ ERR_FAIL_NULL(tile_data);
+ background_region = background_atlas_source->get_tile_texture_region(background_atlas_coords);
+ } else {
+ // If no tile was selected yet, use default size.
+ background_region.size = tile_set->get_tile_size();
+ }
// Draw the focus rectangle.
if (base_control->has_focus()) {
@@ -157,11 +165,14 @@ void GenericTilePolygonEditor::_base_control_draw() {
base_control->draw_set_transform_matrix(xform);
// Draw fill rect under texture region.
- Rect2 texture_rect(-background_region.size / 2 - tile_data->get_texture_origin(), background_region.size);
+ Rect2 texture_rect(-background_region.size / 2, background_region.size);
+ if (tile_data) {
+ texture_rect.position -= tile_data->get_texture_origin();
+ }
base_control->draw_rect(texture_rect, Color(1, 1, 1, 0.3));
// Draw the background.
- if (background_atlas_source->get_texture().is_valid()) {
+ if (tile_data && background_atlas_source->get_texture().is_valid()) {
Size2 region_size = background_region.size;
if (tile_data->get_flip_h()) {
region_size.x = -region_size.x;
@@ -174,8 +185,13 @@ void GenericTilePolygonEditor::_base_control_draw() {
// Compute and draw the grid area.
Rect2 grid_area = Rect2(-base_tile_size / 2, base_tile_size);
- grid_area.expand_to(-background_region.get_size() / 2 - tile_data->get_texture_origin());
- grid_area.expand_to(background_region.get_size() / 2 - tile_data->get_texture_origin());
+ if (tile_data) {
+ grid_area.expand_to(-background_region.get_size() / 2 - tile_data->get_texture_origin());
+ grid_area.expand_to(background_region.get_size() / 2 - tile_data->get_texture_origin());
+ } else {
+ grid_area.expand_to(-background_region.get_size() / 2);
+ grid_area.expand_to(background_region.get_size() / 2);
+ }
base_control->draw_rect(grid_area, Color(1, 1, 1, 0.3), false);
// Draw grid.
diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp
index d3afd25502..4a59530159 100644
--- a/editor/plugins/tiles/tile_map_layer_editor.cpp
+++ b/editor/plugins/tiles/tile_map_layer_editor.cpp
@@ -3630,9 +3630,16 @@ TileMapLayer *TileMapLayerEditor::_get_edited_layer() const {
void TileMapLayerEditor::_find_tile_map_layers_in_scene(Node *p_current, const Node *p_owner, Vector<TileMapLayer *> &r_list) const {
ERR_FAIL_COND(!p_current || !p_owner);
- if (p_current != p_owner && p_current->get_owner() != p_owner) {
- return;
+
+ if (p_current != p_owner) {
+ if (!p_current->get_owner()) {
+ return;
+ }
+ if (p_current->get_owner() != p_owner && !p_owner->is_editable_instance(p_current->get_owner())) {
+ return;
+ }
}
+
TileMapLayer *layer = Object::cast_to<TileMapLayer>(p_current);
if (layer) {
r_list.append(layer);
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index 607c446e1b..2f36198b23 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -133,9 +133,9 @@ void VSRerouteNode::_notification(int p_what) {
connect(SceneStringName(mouse_exited), callable_mp(this, &VSRerouteNode::_on_mouse_exited));
} break;
case NOTIFICATION_DRAW: {
- Vector2 offset = Vector2(0, -16);
+ Vector2 offset = Vector2(0, -16 * EDSCALE);
Color drag_bg_color = get_theme_color(SNAME("drag_background"), SNAME("VSRerouteNode"));
- draw_circle(get_size() * 0.5 + offset, 16, Color(drag_bg_color, selected ? 1 : icon_opacity));
+ draw_circle(get_size() * 0.5 + offset, 16 * EDSCALE, Color(drag_bg_color, selected ? 1 : icon_opacity), true, -1, true);
Ref<Texture2D> icon = get_editor_theme_icon(SNAME("ToolMove"));
Point2 icon_offset = -icon->get_size() * 0.5 + get_size() * 0.5 + offset;
@@ -154,6 +154,7 @@ VSRerouteNode::VSRerouteNode() {
title_lbl->hide();
const Size2 size = Size2(32, 32) * EDSCALE;
+ print_line("VSRerouteNode size: " + size);
Control *slot_area = memnew(Control);
slot_area->set_custom_minimum_size(size);
diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp
index 943e345e97..bdf4e41c5f 100644
--- a/editor/project_settings_editor.cpp
+++ b/editor/project_settings_editor.cpp
@@ -235,28 +235,17 @@ void ProjectSettingsEditor::_select_type(Variant::Type p_type) {
}
void ProjectSettingsEditor::shortcut_input(const Ref<InputEvent> &p_event) {
- ERR_FAIL_COND(p_event.is_null());
- EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
-
const Ref<InputEventKey> k = p_event;
if (k.is_valid() && k->is_pressed()) {
bool handled = false;
if (ED_IS_SHORTCUT("ui_undo", p_event)) {
- String action = undo_redo->get_current_action_name();
- if (!action.is_empty()) {
- EditorNode::get_log()->add_message(vformat(TTR("Undo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
- }
- undo_redo->undo();
+ EditorNode::get_singleton()->undo();
handled = true;
}
if (ED_IS_SHORTCUT("ui_redo", p_event)) {
- undo_redo->redo();
- String action = undo_redo->get_current_action_name();
- if (!action.is_empty()) {
- EditorNode::get_log()->add_message(vformat(TTR("Redo: %s"), action), EditorLog::MSG_TYPE_EDITOR);
- }
+ EditorNode::get_singleton()->redo();
handled = true;
}
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 94bd3e16d3..25de9facb2 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -2276,6 +2276,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
Vector<StringName> former_names;
int inc = 0;
+ bool need_edit = false;
for (int ni = 0; ni < p_nodes.size(); ni++) {
// No undo implemented for this yet.
@@ -2296,7 +2297,11 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
inc--; // If the child will generate a gap when moved, adjust.
}
- if (!same_parent) {
+ if (same_parent) {
+ // When node is reparented to the same parent, EditorSelection does not change.
+ // After hovering another node, the inspector has to be manually updated in this case.
+ need_edit = select_node_hovered_at_end_of_drag;
+ } else {
undo_redo->add_do_method(node->get_parent(), "remove_child", node);
undo_redo->add_do_method(new_parent, "add_child", node, true);
}
@@ -2401,6 +2406,10 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
perform_node_renames(nullptr, &path_renames);
undo_redo->commit_action();
+
+ if (need_edit) {
+ EditorNode::get_singleton()->edit_current();
+ }
}
void SceneTreeDock::_script_created(Ref<Script> p_script) {
diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index 9f0865d950..03752656c0 100644
--- a/editor/themes/editor_theme_manager.cpp
+++ b/editor/themes/editor_theme_manager.cpp
@@ -1665,7 +1665,7 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
// GraphFrame's title Label.
p_theme->set_type_variation("GraphFrameTitleLabel", "Label");
p_theme->set_stylebox(CoreStringName(normal), "GraphFrameTitleLabel", memnew(StyleBoxEmpty));
- p_theme->set_font_size(SceneStringName(font_size), "GraphFrameTitleLabel", 22);
+ p_theme->set_font_size(SceneStringName(font_size), "GraphFrameTitleLabel", 22 * EDSCALE);
p_theme->set_color(SceneStringName(font_color), "GraphFrameTitleLabel", Color(1, 1, 1));
p_theme->set_color("font_shadow_color", "GraphFrameTitleLabel", Color(0, 0, 0, 0));
p_theme->set_color("font_outline_color", "GraphFrameTitleLabel", Color(1, 1, 1));
@@ -1680,7 +1680,7 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
{
Ref<StyleBox> vs_reroute_panel_style = make_empty_stylebox();
Ref<StyleBox> vs_reroute_titlebar_style = vs_reroute_panel_style->duplicate();
- vs_reroute_titlebar_style->set_content_margin_all(16);
+ vs_reroute_titlebar_style->set_content_margin_all(16 * EDSCALE);
p_theme->set_stylebox(SceneStringName(panel), "VSRerouteNode", vs_reroute_panel_style);
p_theme->set_stylebox("panel_selected", "VSRerouteNode", vs_reroute_panel_style);
p_theme->set_stylebox("titlebar", "VSRerouteNode", vs_reroute_titlebar_style);
diff --git a/main/main.cpp b/main/main.cpp
index 9743a8086f..32eb32142d 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -60,6 +60,7 @@
#include "platform/register_platform_apis.h"
#include "scene/main/scene_tree.h"
#include "scene/main/window.h"
+#include "scene/property_list_helper.h"
#include "scene/register_scene_types.h"
#include "scene/resources/packed_scene.h"
#include "scene/theme/theme_db.h"
@@ -793,6 +794,7 @@ void Main::test_cleanup() {
ResourceLoader::remove_custom_loaders();
ResourceSaver::remove_custom_savers();
+ PropertyListHelper::clear_base_helpers();
#ifdef TOOLS_ENABLED
GDExtensionManager::get_singleton()->deinitialize_extensions(GDExtension::INITIALIZATION_LEVEL_EDITOR);
@@ -909,13 +911,11 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
// Benchmark tracking must be done after `OS::get_singleton()->initialize()` as on some
// platforms, it's used to set up the time utilities.
- OS::get_singleton()->benchmark_begin_measure("Startup", "Total");
- OS::get_singleton()->benchmark_begin_measure("Startup", "Setup");
+ OS::get_singleton()->benchmark_begin_measure("Startup", "Main::Setup");
engine = memnew(Engine);
MAIN_PRINT("Main: Initialize CORE");
- OS::get_singleton()->benchmark_begin_measure("Startup", "Core");
register_core_types();
register_core_driver_types();
@@ -2453,8 +2453,6 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
Thread::release_main_thread(); // If setup2() is called from another thread, that one will become main thread, so preventively release this one.
set_current_thread_safe_for_nodes(false);
- OS::get_singleton()->benchmark_end_measure("Startup", "Core");
-
#if defined(STEAMAPI_ENABLED)
if (editor || project_manager) {
steam_tracker = memnew(SteamTracker);
@@ -2465,7 +2463,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
return setup2();
}
- OS::get_singleton()->benchmark_end_measure("Startup", "Setup");
+ OS::get_singleton()->benchmark_end_measure("Startup", "Main::Setup");
return OK;
error:
@@ -2519,7 +2517,7 @@ error:
}
OS::get_singleton()->benchmark_end_measure("Startup", "Core");
- OS::get_singleton()->benchmark_end_measure("Startup", "Setup");
+ OS::get_singleton()->benchmark_end_measure("Startup", "Main::Setup");
#if defined(STEAMAPI_ENABLED)
if (steam_tracker) {
@@ -2553,6 +2551,8 @@ Error _parse_resource_dummy(void *p_data, VariantParser::Stream *p_stream, Ref<R
}
Error Main::setup2(bool p_show_boot_logo) {
+ OS::get_singleton()->benchmark_begin_measure("Startup", "Main::Setup2");
+
Thread::make_main_thread(); // Make whatever thread call this the main thread.
set_current_thread_safe_for_nodes(true);
@@ -2941,6 +2941,8 @@ Error Main::setup2(bool p_show_boot_logo) {
id->set_emulate_mouse_from_touch(bool(GLOBAL_DEF_BASIC("input_devices/pointing/emulate_mouse_from_touch", true)));
}
+ GLOBAL_DEF("input_devices/buffering/android/use_accumulated_input", true);
+ GLOBAL_DEF("input_devices/buffering/android/use_input_buffering", true);
GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_long_press_as_right_click", false);
GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_pan_and_scale_gestures", false);
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "input_devices/pointing/android/rotary_input_scroll_axis", PROPERTY_HINT_ENUM, "Horizontal,Vertical"), 1);
@@ -3149,7 +3151,7 @@ Error Main::setup2(bool p_show_boot_logo) {
print_verbose("EDITOR API HASH: " + uitos(ClassDB::get_api_hash(ClassDB::API_EDITOR)));
MAIN_PRINT("Main: Done");
- OS::get_singleton()->benchmark_end_measure("Startup", "Setup");
+ OS::get_singleton()->benchmark_end_measure("Startup", "Main::Setup2");
return OK;
}
@@ -3230,6 +3232,8 @@ static MainTimerSync main_timer_sync;
// and should move on to `OS::run`, and EXIT_FAILURE otherwise for
// an early exit with that error code.
int Main::start() {
+ OS::get_singleton()->benchmark_begin_measure("Startup", "Main::Start");
+
ERR_FAIL_COND_V(!_start_success, false);
bool has_icon = false;
@@ -3953,7 +3957,7 @@ int Main::start() {
}
}
- OS::get_singleton()->benchmark_end_measure("Startup", "Total");
+ OS::get_singleton()->benchmark_end_measure("Startup", "Main::Start");
OS::get_singleton()->benchmark_dump();
return EXIT_SUCCESS;
@@ -4041,6 +4045,7 @@ bool Main::iteration() {
}
Engine::get_singleton()->_in_physics = true;
+ Engine::get_singleton()->_physics_frames++;
uint64_t physics_begin = OS::get_singleton()->get_ticks_usec();
@@ -4088,7 +4093,6 @@ bool Main::iteration() {
physics_process_ticks = MAX(physics_process_ticks, OS::get_singleton()->get_ticks_usec() - physics_begin); // keep the largest one for reference
physics_process_max = MAX(OS::get_singleton()->get_ticks_usec() - physics_begin, physics_process_max);
- Engine::get_singleton()->_physics_frames++;
Engine::get_singleton()->_in_physics = false;
}
@@ -4221,7 +4225,7 @@ void Main::force_redraw() {
* The order matters as some of those steps are linked with each other.
*/
void Main::cleanup(bool p_force) {
- OS::get_singleton()->benchmark_begin_measure("Shutdown", "Total");
+ OS::get_singleton()->benchmark_begin_measure("Shutdown", "Main::Cleanup");
if (!p_force) {
ERR_FAIL_COND(!_start_success);
}
@@ -4244,6 +4248,7 @@ void Main::cleanup(bool p_force) {
ResourceLoader::remove_custom_loaders();
ResourceSaver::remove_custom_savers();
+ PropertyListHelper::clear_base_helpers();
// Flush before uninitializing the scene, but delete the MessageQueue as late as possible.
message_queue->flush();
@@ -4379,7 +4384,7 @@ void Main::cleanup(bool p_force) {
unregister_core_types();
- OS::get_singleton()->benchmark_end_measure("Shutdown", "Total");
+ OS::get_singleton()->benchmark_end_measure("Shutdown", "Main::Cleanup");
OS::get_singleton()->benchmark_dump();
OS::get_singleton()->finalize_core();
diff --git a/methods.py b/methods.py
index 99c47ca077..b0f7df9ab2 100644
--- a/methods.py
+++ b/methods.py
@@ -268,7 +268,7 @@ def get_version_info(module_version_string="", silent=False):
if os.path.exists(".git"):
try:
version_info["git_timestamp"] = subprocess.check_output(
- ["git", "log", "-1", "--pretty=format:%ct", githash]
+ ["git", "log", "-1", "--pretty=format:%ct", "--no-show-signature", githash]
).decode("utf-8")
except (subprocess.CalledProcessError, OSError):
# `git` not found in PATH.
@@ -648,6 +648,7 @@ def detect_visual_c_compiler_version(tools_env):
def find_visual_c_batch_file(env):
+ # TODO: We should investigate if we can avoid relying on SCons internals here.
from SCons.Tool.MSCommon.vc import find_batch_file, find_vc_pdir, get_default_version, get_host_target
msvc_version = get_default_version(env)
@@ -661,10 +662,11 @@ def find_visual_c_batch_file(env):
if env.scons_version < (4, 6, 0):
return find_batch_file(env, msvc_version, host_platform, target_platform)[0]
- # Scons 4.6.0+ removed passing env, so we need to get the product_dir ourselves first,
+ # SCons 4.6.0+ removed passing env, so we need to get the product_dir ourselves first,
# then pass that as the last param instead of env as the first param as before.
- # We should investigate if we can avoid relying on SCons internals here.
- product_dir = find_vc_pdir(env, msvc_version)
+ # Param names need to be explicit, as they were shuffled around in SCons 4.8.0.
+ product_dir = find_vc_pdir(msvc_version=msvc_version, env=env)
+
return find_batch_file(msvc_version, host_platform, target_platform, product_dir)[0]
diff --git a/misc/dist/html/editor.html b/misc/dist/html/editor.html
index 5959b7b664..3a22055546 100644
--- a/misc/dist/html/editor.html
+++ b/misc/dist/html/editor.html
@@ -227,7 +227,6 @@ a:active {
line-height: 1.3;
visibility: visible;
padding: 4px 6px;
- visibility: visible;
}
</style>
</head>
diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected
index 7b93df70fa..4b0d22a1aa 100644
--- a/misc/extension_api_validation/4.2-stable.expected
+++ b/misc/extension_api_validation/4.2-stable.expected
@@ -372,3 +372,10 @@ GH-91382
Validate extension JSON: Error: Field 'classes/AudioStreamPlaybackPolyphonic/methods/play_stream/arguments': size changed value in new API, from 4 to 6.
Optional arguments added. Compatibility methods registered.
+
+
+GH-93982
+--------
+Validate extension JSON: Error: Field 'classes/Sprite3D/properties/frame_coords': type changed value in new API, from "Vector2" to "Vector2i".
+
+The type was wrong to begin with and has been corrected. Vector2 and Vector2i are convertible, so it should be compatible.
diff --git a/modules/enet/doc_classes/ENetConnection.xml b/modules/enet/doc_classes/ENetConnection.xml
index 5795dd8976..ebd1577172 100644
--- a/modules/enet/doc_classes/ENetConnection.xml
+++ b/modules/enet/doc_classes/ENetConnection.xml
@@ -141,7 +141,7 @@
<return type="Array" />
<param index="0" name="timeout" type="int" default="0" />
<description>
- Waits for events on the host specified and shuttles packets between the host and its peers. The returned [Array] will have 4 elements. An [enum EventType], the [ENetPacketPeer] which generated the event, the event associated data (if any), the event associated channel (if any). If the generated event is [constant EVENT_RECEIVE], the received packet will be queued to the associated [ENetPacketPeer].
+ Waits for events on the specified host and shuttles packets between the host and its peers, with the given [param timeout] (in milliseconds). The returned [Array] will have 4 elements. An [enum EventType], the [ENetPacketPeer] which generated the event, the event associated data (if any), the event associated channel (if any). If the generated event is [constant EVENT_RECEIVE], the received packet will be queued to the associated [ENetPacketPeer].
Call this function regularly to handle connections, disconnections, and to receive new packets.
</description>
</method>
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index f557727718..b58b44973e 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -1959,11 +1959,14 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
break;
}
- if (base.value.in(index.value)) {
- Variant value = base.value.get(index.value);
- r_type = _type_from_variant(value, p_context);
- found = true;
- break;
+ {
+ bool valid;
+ Variant value = base.value.get(index.value, &valid);
+ if (valid) {
+ r_type = _type_from_variant(value, p_context);
+ found = true;
+ break;
+ }
}
// Look if it is a dictionary node.
diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp
index 626ef6ccb0..2162a727b3 100644
--- a/modules/gdscript/gdscript_lambda_callable.cpp
+++ b/modules/gdscript/gdscript_lambda_callable.cpp
@@ -84,7 +84,7 @@ int GDScriptLambdaCallable::get_argument_count(bool &r_is_valid) const {
return 0;
}
r_is_valid = true;
- return function->get_argument_count();
+ return function->get_argument_count() - captures.size();
}
void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
@@ -198,13 +198,17 @@ ObjectID GDScriptLambdaSelfCallable::get_object() const {
return object->get_instance_id();
}
+StringName GDScriptLambdaSelfCallable::get_method() const {
+ return function->get_name();
+}
+
int GDScriptLambdaSelfCallable::get_argument_count(bool &r_is_valid) const {
if (function == nullptr) {
r_is_valid = false;
return 0;
}
r_is_valid = true;
- return function->get_argument_count();
+ return function->get_argument_count() - captures.size();
}
void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h
index 45c0235913..2d27b8d679 100644
--- a/modules/gdscript/gdscript_lambda_callable.h
+++ b/modules/gdscript/gdscript_lambda_callable.h
@@ -87,6 +87,7 @@ public:
CompareEqualFunc get_compare_equal_func() const override;
CompareLessFunc get_compare_less_func() const override;
ObjectID get_object() const override;
+ StringName get_method() const override;
int get_argument_count(bool &r_is_valid) const override;
void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index a1ea94667d..433f767f1e 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -413,7 +413,7 @@ Error GDScriptParser::parse_binary(const Vector<uint8_t> &p_binary, const String
}
tokenizer = buffer_tokenizer;
- script_path = p_script_path;
+ script_path = p_script_path.simplify_path();
current = tokenizer->scan();
// Avoid error or newline as the first token.
// The latter can mess with the parser when opening files filled exclusively with comments and newlines.
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index 5d1805696d..912367764b 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -550,9 +550,22 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
return _get_default_variant_for_data_type(return_type);
}
if (argument_types[i].kind == GDScriptDataType::BUILTIN) {
- Variant arg;
- Variant::construct(argument_types[i].builtin_type, arg, &p_args[i], 1, r_err);
- memnew_placement(&stack[i + 3], Variant(arg));
+ if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) {
+ const GDScriptDataType &arg_type = argument_types[i].container_element_types[0];
+ Array array(p_args[i]->operator Array(), arg_type.builtin_type, arg_type.native_type, arg_type.script_type);
+ memnew_placement(&stack[i + 3], Variant(array));
+ } else {
+ Variant variant;
+ Variant::construct(argument_types[i].builtin_type, variant, &p_args[i], 1, r_err);
+ if (unlikely(r_err.error)) {
+ r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_err.argument = i;
+ r_err.expected = argument_types[i].builtin_type;
+ call_depth--;
+ return _get_default_variant_for_data_type(return_type);
+ }
+ memnew_placement(&stack[i + 3], Variant(variant));
+ }
} else {
memnew_placement(&stack[i + 3], Variant(*p_args[i]));
}
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out
index 7b9f1066b0..9b38957101 100644
--- a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out
@@ -1,4 +1,5 @@
GDTEST_RUNTIME_ERROR
>> ERROR
>> Method/function failed.
+>> Unable to convert array index 0 from "Object" to "Object".
not ok
diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_bind_argument_count.gd b/modules/gdscript/tests/scripts/runtime/features/lambda_bind_argument_count.gd
new file mode 100644
index 0000000000..67225cad6a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/lambda_bind_argument_count.gd
@@ -0,0 +1,18 @@
+# https://github.com/godotengine/godot/issues/93952
+
+func foo():
+ pass
+
+func test():
+ var a: int
+
+ var lambda_self := func (x: int) -> void:
+ foo()
+ print(a, x)
+
+ print(lambda_self.get_argument_count()) # Should print 1.
+
+ var lambda_non_self := func (x: int) -> void:
+ print(a, x)
+
+ print(lambda_non_self.get_argument_count()) # Should print 1.
diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_bind_argument_count.out b/modules/gdscript/tests/scripts/runtime/features/lambda_bind_argument_count.out
new file mode 100644
index 0000000000..04b4638adf
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/lambda_bind_argument_count.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+1
+1
diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.gd b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.gd
new file mode 100644
index 0000000000..160e43a797
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.gd
@@ -0,0 +1,21 @@
+# https://github.com/godotengine/godot/issues/94074
+
+func foo():
+ pass
+
+func test():
+ var lambda_self := func test() -> void:
+ foo()
+ var anon_lambda_self := func() -> void:
+ foo()
+
+ print(lambda_self.get_method()) # Should print "test".
+ print(anon_lambda_self.get_method()) # Should print "<anonymous lambda>".
+
+ var lambda_non_self := func test() -> void:
+ pass
+ var anon_lambda_non_self := func() -> void:
+ pass
+
+ print(lambda_non_self.get_method()) # Should print "test".
+ print(anon_lambda_non_self.get_method()) # Should print "<anonymous lambda>".
diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.out b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.out
new file mode 100644
index 0000000000..17ee47fca2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+test
+<anonymous lambda>
+test
+<anonymous lambda>
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.gd b/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.gd
new file mode 100644
index 0000000000..13f2c3b956
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.gd
@@ -0,0 +1,7 @@
+# GH-93990
+
+func test_param(array: Array[String]) -> void:
+ print(array.get_typed_builtin() == TYPE_STRING)
+
+func test() -> void:
+ test_param(PackedStringArray())
diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.out b/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.out
new file mode 100644
index 0000000000..55482c2b52
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+true
diff --git a/modules/gltf/doc_classes/GLTFAccessor.xml b/modules/gltf/doc_classes/GLTFAccessor.xml
index 54762faed7..dd059e6b79 100644
--- a/modules/gltf/doc_classes/GLTFAccessor.xml
+++ b/modules/gltf/doc_classes/GLTFAccessor.xml
@@ -12,7 +12,7 @@
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link>
</tutorials>
<members>
- <member name="accessor_type" type="int" setter="set_accessor_type" getter="get_accessor_type" default="0">
+ <member name="accessor_type" type="int" setter="set_accessor_type" getter="get_accessor_type" enum="GLTFAccessor.GLTFAccessorType" default="0">
The GLTF accessor type as an enum. Possible values are 0 for "SCALAR", 1 for "VEC2", 2 for "VEC3", 3 for "VEC4", 4 for "MAT2", 5 for "MAT3", and 6 for "MAT4".
</member>
<member name="buffer_view" type="int" setter="set_buffer_view" getter="get_buffer_view" default="-1">
@@ -54,8 +54,31 @@
<member name="sparse_values_byte_offset" type="int" setter="set_sparse_values_byte_offset" getter="get_sparse_values_byte_offset" default="0">
The offset relative to the start of the bufferView in bytes.
</member>
- <member name="type" type="int" setter="set_type" getter="get_type" default="0" deprecated="Use [member accessor_type] instead.">
+ <member name="type" type="int" setter="set_type" getter="get_type" deprecated="Use [member accessor_type] instead.">
The GLTF accessor type as an enum. Use [member accessor_type] instead.
</member>
</members>
+ <constants>
+ <constant name="TYPE_SCALAR" value="0" enum="GLTFAccessorType">
+ Accessor type "SCALAR". For the glTF object model, this can be used to map to a single float, int, or bool value, or a float array.
+ </constant>
+ <constant name="TYPE_VEC2" value="1" enum="GLTFAccessorType">
+ Accessor type "VEC2". For the glTF object model, this maps to "float2", represented in the glTF JSON as an array of two floats.
+ </constant>
+ <constant name="TYPE_VEC3" value="2" enum="GLTFAccessorType">
+ Accessor type "VEC3". For the glTF object model, this maps to "float3", represented in the glTF JSON as an array of three floats.
+ </constant>
+ <constant name="TYPE_VEC4" value="3" enum="GLTFAccessorType">
+ Accessor type "VEC4". For the glTF object model, this maps to "float4", represented in the glTF JSON as an array of four floats.
+ </constant>
+ <constant name="TYPE_MAT2" value="4" enum="GLTFAccessorType">
+ Accessor type "MAT2". For the glTF object model, this maps to "float2x2", represented in the glTF JSON as an array of four floats.
+ </constant>
+ <constant name="TYPE_MAT3" value="5" enum="GLTFAccessorType">
+ Accessor type "MAT3". For the glTF object model, this maps to "float3x3", represented in the glTF JSON as an array of nine floats.
+ </constant>
+ <constant name="TYPE_MAT4" value="6" enum="GLTFAccessorType">
+ Accessor type "MAT4". For the glTF object model, this maps to "float4x4", represented in the glTF JSON as an array of sixteen floats.
+ </constant>
+ </constants>
</class>
diff --git a/modules/gltf/editor/editor_import_blend_runner.cpp b/modules/gltf/editor/editor_import_blend_runner.cpp
index 330310d92a..22c8adfe88 100644
--- a/modules/gltf/editor/editor_import_blend_runner.cpp
+++ b/modules/gltf/editor/editor_import_blend_runner.cpp
@@ -43,6 +43,7 @@ from xmlrpc.server import SimpleXMLRPCServer
req = threading.Condition()
res = threading.Condition()
info = None
+export_err = None
def xmlrpc_server():
server = SimpleXMLRPCServer(('127.0.0.1', %d))
server.register_function(export_gltf)
@@ -54,6 +55,10 @@ def export_gltf(opts):
req.notify()
with res:
res.wait()
+ if export_err:
+ raise export_err
+ # Important to return a value to prevent the error 'cannot marshal None unless allow_none is enabled'.
+ return 'BLENDER_GODOT_EXPORT_SUCCESSFUL'
if bpy.app.version < (3, 0, 0):
print('Blender 3.0 or higher is required.', file=sys.stderr)
threading.Thread(target=xmlrpc_server).start()
@@ -64,12 +69,13 @@ while True:
method, opts = info
if method == 'export_gltf':
try:
+ export_err = None
bpy.ops.wm.open_mainfile(filepath=opts['path'])
if opts['unpack_all']:
bpy.ops.file.unpack_all(method='USE_LOCAL')
bpy.ops.export_scene.gltf(**opts['gltf_options'])
- except:
- pass
+ except Exception as e:
+ export_err = e
info = None
with res:
res.notify()
@@ -184,7 +190,9 @@ Error EditorImportBlendRunner::do_import(const Dictionary &p_options) {
EditorSettings::get_singleton()->set_manually("filesystem/import/blender/rpc_port", 0);
rpc_port = 0;
}
- err = do_import_direct(p_options);
+ if (err != ERR_QUERY_FAILED) {
+ err = do_import_direct(p_options);
+ }
}
return err;
} else {
@@ -259,6 +267,7 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) {
// Wait for response.
bool done = false;
+ PackedByteArray response;
while (!done) {
status = client->get_status();
switch (status) {
@@ -268,7 +277,10 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) {
}
case HTTPClient::STATUS_BODY: {
client->poll();
- // Parse response here if needed. For now we can just ignore it.
+ response.append_array(client->read_response_body_chunk());
+ break;
+ }
+ case HTTPClient::STATUS_CONNECTED: {
done = true;
break;
}
@@ -278,9 +290,56 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) {
}
}
+ String response_text = "No response from Blender.";
+ if (response.size() > 0) {
+ response_text = String::utf8((const char *)response.ptr(), response.size());
+ }
+
+ if (client->get_response_code() != HTTPClient::RESPONSE_OK) {
+ ERR_FAIL_V_MSG(ERR_QUERY_FAILED, vformat("Error received from Blender - status code: %s, error: %s", client->get_response_code(), response_text));
+ } else if (response_text.find("BLENDER_GODOT_EXPORT_SUCCESSFUL") < 0) {
+ // Previous versions of Godot used a Python script where the RPC function did not return
+ // a value, causing the error 'cannot marshal None unless allow_none is enabled'.
+ // If an older version of Godot is running and has started Blender with this script,
+ // we will receive the error, but there's a good chance that the import was successful.
+ // We are discarding this error to maintain backward compatibility and prevent situations
+ // where the user needs to close the older version of Godot or kill Blender.
+ if (response_text.find("cannot marshal None unless allow_none is enabled") < 0) {
+ String error_message;
+ if (_extract_error_message_xml(response, error_message)) {
+ ERR_FAIL_V_MSG(ERR_QUERY_FAILED, vformat("Blender exportation failed: %s", error_message));
+ } else {
+ ERR_FAIL_V_MSG(ERR_QUERY_FAILED, vformat("Blender exportation failed: %s", response_text));
+ }
+ }
+ }
+
return OK;
}
+bool EditorImportBlendRunner::_extract_error_message_xml(const Vector<uint8_t> &p_response_data, String &r_error_message) {
+ // Based on RPC Xml spec from: https://xmlrpc.com/spec.md
+ Ref<XMLParser> parser = memnew(XMLParser);
+ Error err = parser->open_buffer(p_response_data);
+ if (err) {
+ return false;
+ }
+
+ r_error_message = String();
+ while (parser->read() == OK) {
+ if (parser->get_node_type() == XMLParser::NODE_TEXT) {
+ if (parser->get_node_data().size()) {
+ if (r_error_message.size()) {
+ r_error_message += " ";
+ }
+ r_error_message += parser->get_node_data().trim_suffix("\n");
+ }
+ }
+ }
+
+ return r_error_message.size();
+}
+
Error EditorImportBlendRunner::do_import_direct(const Dictionary &p_options) {
// Export glTF directly.
String python = vformat(PYTHON_SCRIPT_DIRECT, dict_to_python(p_options));
diff --git a/modules/gltf/editor/editor_import_blend_runner.h b/modules/gltf/editor/editor_import_blend_runner.h
index 626f3c9eba..b3b49ebfb2 100644
--- a/modules/gltf/editor/editor_import_blend_runner.h
+++ b/modules/gltf/editor/editor_import_blend_runner.h
@@ -47,6 +47,7 @@ class EditorImportBlendRunner : public Node {
void _resources_reimported(const PackedStringArray &p_files);
void _kill_blender();
void _notification(int p_what);
+ bool _extract_error_message_xml(const Vector<uint8_t> &p_response_data, String &r_error_message);
protected:
int rpc_port = 0;
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index 79a2184745..b474128fd6 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -132,12 +132,10 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
}
#endif
- source_global = source_global.c_escape();
-
const String blend_basename = p_path.get_file().get_basename();
const String sink = ProjectSettings::get_singleton()->get_imported_files_path().path_join(
vformat("%s-%s.gltf", blend_basename, p_path.md5_text()));
- const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink).c_escape();
+ const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink);
// Handle configuration options.
@@ -188,10 +186,18 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
} else {
parameters_map["export_lights"] = false;
}
- if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) {
- parameters_map["export_colors"] = true;
+ if (blender_major_version > 4 || (blender_major_version == 4 && blender_minor_version >= 2)) {
+ if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) {
+ parameters_map["export_vertex_color"] = "MATERIAL";
+ } else {
+ parameters_map["export_vertex_color"] = "NONE";
+ }
} else {
- parameters_map["export_colors"] = false;
+ if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) {
+ parameters_map["export_colors"] = true;
+ } else {
+ parameters_map["export_colors"] = false;
+ }
}
if (p_options.has(SNAME("blender/nodes/visible"))) {
int32_t visible = p_options["blender/nodes/visible"];
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index e0bdd4cf33..c0232e6d0c 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -934,58 +934,58 @@ Error GLTFDocument::_encode_accessors(Ref<GLTFState> p_state) {
return OK;
}
-String GLTFDocument::_get_accessor_type_name(const GLTFAccessorType p_accessor_type) {
- if (p_accessor_type == GLTFAccessorType::TYPE_SCALAR) {
+String GLTFDocument::_get_accessor_type_name(const GLTFAccessor::GLTFAccessorType p_accessor_type) {
+ if (p_accessor_type == GLTFAccessor::TYPE_SCALAR) {
return "SCALAR";
}
- if (p_accessor_type == GLTFAccessorType::TYPE_VEC2) {
+ if (p_accessor_type == GLTFAccessor::TYPE_VEC2) {
return "VEC2";
}
- if (p_accessor_type == GLTFAccessorType::TYPE_VEC3) {
+ if (p_accessor_type == GLTFAccessor::TYPE_VEC3) {
return "VEC3";
}
- if (p_accessor_type == GLTFAccessorType::TYPE_VEC4) {
+ if (p_accessor_type == GLTFAccessor::TYPE_VEC4) {
return "VEC4";
}
- if (p_accessor_type == GLTFAccessorType::TYPE_MAT2) {
+ if (p_accessor_type == GLTFAccessor::TYPE_MAT2) {
return "MAT2";
}
- if (p_accessor_type == GLTFAccessorType::TYPE_MAT3) {
+ if (p_accessor_type == GLTFAccessor::TYPE_MAT3) {
return "MAT3";
}
- if (p_accessor_type == GLTFAccessorType::TYPE_MAT4) {
+ if (p_accessor_type == GLTFAccessor::TYPE_MAT4) {
return "MAT4";
}
ERR_FAIL_V("SCALAR");
}
-GLTFAccessorType GLTFDocument::_get_accessor_type_from_str(const String &p_string) {
+GLTFAccessor::GLTFAccessorType GLTFDocument::_get_accessor_type_from_str(const String &p_string) {
if (p_string == "SCALAR") {
- return GLTFAccessorType::TYPE_SCALAR;
+ return GLTFAccessor::TYPE_SCALAR;
}
if (p_string == "VEC2") {
- return GLTFAccessorType::TYPE_VEC2;
+ return GLTFAccessor::TYPE_VEC2;
}
if (p_string == "VEC3") {
- return GLTFAccessorType::TYPE_VEC3;
+ return GLTFAccessor::TYPE_VEC3;
}
if (p_string == "VEC4") {
- return GLTFAccessorType::TYPE_VEC4;
+ return GLTFAccessor::TYPE_VEC4;
}
if (p_string == "MAT2") {
- return GLTFAccessorType::TYPE_MAT2;
+ return GLTFAccessor::TYPE_MAT2;
}
if (p_string == "MAT3") {
- return GLTFAccessorType::TYPE_MAT3;
+ return GLTFAccessor::TYPE_MAT3;
}
if (p_string == "MAT4") {
- return GLTFAccessorType::TYPE_MAT4;
+ return GLTFAccessor::TYPE_MAT4;
}
- ERR_FAIL_V(GLTFAccessorType::TYPE_SCALAR);
+ ERR_FAIL_V(GLTFAccessor::TYPE_SCALAR);
}
Error GLTFDocument::_parse_accessors(Ref<GLTFState> p_state) {
@@ -1088,7 +1088,7 @@ String GLTFDocument::_get_component_type_name(const uint32_t p_component) {
return "<Error>";
}
-Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_src, const int p_count, const GLTFAccessorType p_accessor_type, const int p_component_type, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex, GLTFBufferViewIndex &r_accessor, const bool p_for_vertex_indices) {
+Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_src, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_type, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex, GLTFBufferViewIndex &r_accessor, const bool p_for_vertex_indices) {
const int component_count_for_type[7] = {
1, 2, 3, 4, 4, 9, 16
};
@@ -1103,18 +1103,18 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
switch (p_component_type) {
case COMPONENT_TYPE_BYTE:
case COMPONENT_TYPE_UNSIGNED_BYTE: {
- if (p_accessor_type == TYPE_MAT2) {
+ if (p_accessor_type == GLTFAccessor::TYPE_MAT2) {
skip_every = 2;
skip_bytes = 2;
}
- if (p_accessor_type == TYPE_MAT3) {
+ if (p_accessor_type == GLTFAccessor::TYPE_MAT3) {
skip_every = 3;
skip_bytes = 1;
}
} break;
case COMPONENT_TYPE_SHORT:
case COMPONENT_TYPE_UNSIGNED_SHORT: {
- if (p_accessor_type == TYPE_MAT3) {
+ if (p_accessor_type == GLTFAccessor::TYPE_MAT3) {
skip_every = 6;
skip_bytes = 4;
}
@@ -1296,7 +1296,7 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_
return OK;
}
-Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, const GLTFBufferViewIndex p_buffer_view, const int p_skip_every, const int p_skip_bytes, const int p_element_size, const int p_count, const GLTFAccessorType p_accessor_type, const int p_component_count, const int p_component_type, const int p_component_size, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex) {
+Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, const GLTFBufferViewIndex p_buffer_view, const int p_skip_every, const int p_skip_bytes, const int p_element_size, const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_count, const int p_component_type, const int p_component_size, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex) {
const Ref<GLTFBufferView> bv = p_state->buffer_views[p_buffer_view];
int stride = p_element_size;
@@ -1427,12 +1427,12 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF
switch (a->component_type) {
case COMPONENT_TYPE_BYTE:
case COMPONENT_TYPE_UNSIGNED_BYTE: {
- if (a->accessor_type == TYPE_MAT2) {
+ if (a->accessor_type == GLTFAccessor::TYPE_MAT2) {
skip_every = 2;
skip_bytes = 2;
element_size = 8; //override for this case
}
- if (a->accessor_type == TYPE_MAT3) {
+ if (a->accessor_type == GLTFAccessor::TYPE_MAT3) {
skip_every = 3;
skip_bytes = 1;
element_size = 12; //override for this case
@@ -1440,7 +1440,7 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF
} break;
case COMPONENT_TYPE_SHORT:
case COMPONENT_TYPE_UNSIGNED_SHORT: {
- if (a->accessor_type == TYPE_MAT3) {
+ if (a->accessor_type == GLTFAccessor::TYPE_MAT3) {
skip_every = 6;
skip_bytes = 4;
element_size = 16; //override for this case
@@ -1474,7 +1474,7 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTF
indices.resize(a->sparse_count);
const int indices_component_size = _get_component_type_size(a->sparse_indices_component_type);
- Error err = _decode_buffer_view(p_state, indices.ptrw(), a->sparse_indices_buffer_view, 0, 0, indices_component_size, a->sparse_count, TYPE_SCALAR, 1, a->sparse_indices_component_type, indices_component_size, false, a->sparse_indices_byte_offset, false);
+ Error err = _decode_buffer_view(p_state, indices.ptrw(), a->sparse_indices_buffer_view, 0, 0, indices_component_size, a->sparse_count, GLTFAccessor::TYPE_SCALAR, 1, a->sparse_indices_component_type, indices_component_size, false, a->sparse_indices_byte_offset, false);
if (err != OK) {
return Vector<double>();
}
@@ -1536,7 +1536,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> p_state,
p_state->buffers.push_back(Vector<uint8_t>());
}
int64_t size = p_state->buffers[0].size();
- const GLTFAccessorType accessor_type = GLTFAccessorType::TYPE_SCALAR;
+ const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_SCALAR;
int component_type;
if (max_index > 65535 || p_for_vertex) {
component_type = GLTFDocument::COMPONENT_TYPE_INT;
@@ -1650,7 +1650,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec2(Ref<GLTFState> p_state,
p_state->buffers.push_back(Vector<uint8_t>());
}
int64_t size = p_state->buffers[0].size();
- const GLTFAccessorType accessor_type = GLTFAccessorType::TYPE_VEC2;
+ const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC2;
const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
accessor->max = type_max;
@@ -1703,7 +1703,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_color(Ref<GLTFState> p_state
p_state->buffers.push_back(Vector<uint8_t>());
}
int64_t size = p_state->buffers[0].size();
- const GLTFAccessorType accessor_type = GLTFAccessorType::TYPE_VEC4;
+ const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4;
const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
accessor->max = type_max;
@@ -1770,7 +1770,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_weights(Ref<GLTFState> p_sta
p_state->buffers.push_back(Vector<uint8_t>());
}
int64_t size = p_state->buffers[0].size();
- const GLTFAccessorType accessor_type = GLTFAccessorType::TYPE_VEC4;
+ const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4;
const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
accessor->max = type_max;
@@ -1821,7 +1821,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_joints(Ref<GLTFState> p_stat
p_state->buffers.push_back(Vector<uint8_t>());
}
int64_t size = p_state->buffers[0].size();
- const GLTFAccessorType accessor_type = GLTFAccessorType::TYPE_VEC4;
+ const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4;
const int component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT;
accessor->max = type_max;
@@ -1874,7 +1874,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_quaternions(Ref<GLTFState> p
p_state->buffers.push_back(Vector<uint8_t>());
}
int64_t size = p_state->buffers[0].size();
- const GLTFAccessorType accessor_type = GLTFAccessorType::TYPE_VEC4;
+ const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC4;
const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
accessor->max = type_max;
@@ -1949,7 +1949,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> p_stat
p_state->buffers.push_back(Vector<uint8_t>());
}
int64_t size = p_state->buffers[0].size();
- const GLTFAccessorType accessor_type = GLTFAccessorType::TYPE_SCALAR;
+ const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_SCALAR;
const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
accessor->max = type_max;
@@ -1999,7 +1999,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec3(Ref<GLTFState> p_state,
p_state->buffers.push_back(Vector<uint8_t>());
}
int64_t size = p_state->buffers[0].size();
- const GLTFAccessorType accessor_type = GLTFAccessorType::TYPE_VEC3;
+ const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC3;
const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
accessor->max = type_max;
@@ -2075,7 +2075,7 @@ GLTFAccessorIndex GLTFDocument::_encode_sparse_accessor_as_vec3(Ref<GLTFState> p
p_state->buffers.push_back(Vector<uint8_t>());
}
int64_t size = p_state->buffers[0].size();
- const GLTFAccessorType accessor_type = GLTFAccessorType::TYPE_VEC3;
+ const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_VEC3;
const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
sparse_accessor->normalized = false;
@@ -2103,7 +2103,7 @@ GLTFAccessorIndex GLTFDocument::_encode_sparse_accessor_as_vec3(Ref<GLTFState> p
} else {
sparse_accessor->sparse_indices_component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT;
}
- if (_encode_buffer_view(p_state, changed_indices.ptr(), changed_indices.size(), GLTFAccessorType::TYPE_SCALAR, sparse_accessor->sparse_indices_component_type, sparse_accessor->normalized, sparse_accessor->sparse_indices_byte_offset, false, buffer_view_i_indices) != OK) {
+ if (_encode_buffer_view(p_state, changed_indices.ptr(), changed_indices.size(), GLTFAccessor::TYPE_SCALAR, sparse_accessor->sparse_indices_component_type, sparse_accessor->normalized, sparse_accessor->sparse_indices_byte_offset, false, buffer_view_i_indices) != OK) {
return -1;
}
// We use changed_indices.size() here, because we must pass the number of vec3 values rather than the number of components.
@@ -2180,7 +2180,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_xform(Ref<GLTFState> p_state
p_state->buffers.push_back(Vector<uint8_t>());
}
int64_t size = p_state->buffers[0].size();
- const GLTFAccessorType accessor_type = GLTFAccessorType::TYPE_MAT4;
+ const GLTFAccessor::GLTFAccessorType accessor_type = GLTFAccessor::TYPE_MAT4;
const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT;
accessor->max = type_max;
@@ -2234,9 +2234,9 @@ Vector<Color> GLTFDocument::_decode_accessor_as_color(Ref<GLTFState> p_state, co
}
const int accessor_type = p_state->accessors[p_accessor]->accessor_type;
- ERR_FAIL_COND_V(!(accessor_type == TYPE_VEC3 || accessor_type == TYPE_VEC4), ret);
+ ERR_FAIL_COND_V(!(accessor_type == GLTFAccessor::TYPE_VEC3 || accessor_type == GLTFAccessor::TYPE_VEC4), ret);
int vec_len = 3;
- if (accessor_type == TYPE_VEC4) {
+ if (accessor_type == GLTFAccessor::TYPE_VEC4) {
vec_len = 4;
}
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index 4f92ceccca..d37544750d 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -111,7 +111,7 @@ private:
int _get_component_type_size(const int p_component_type);
Error _parse_scenes(Ref<GLTFState> p_state);
Error _parse_nodes(Ref<GLTFState> p_state);
- String _get_accessor_type_name(const GLTFAccessorType p_accessor_type);
+ String _get_accessor_type_name(const GLTFAccessor::GLTFAccessorType p_accessor_type);
String _sanitize_animation_name(const String &p_name);
String _gen_unique_animation_name(Ref<GLTFState> p_state, const String &p_name);
String _sanitize_bone_name(const String &p_name);
@@ -131,13 +131,13 @@ private:
void _compute_node_heights(Ref<GLTFState> p_state);
Error _parse_buffers(Ref<GLTFState> p_state, const String &p_base_path);
Error _parse_buffer_views(Ref<GLTFState> p_state);
- GLTFAccessorType _get_accessor_type_from_str(const String &p_string);
+ GLTFAccessor::GLTFAccessorType _get_accessor_type_from_str(const String &p_string);
Error _parse_accessors(Ref<GLTFState> p_state);
Error _decode_buffer_view(Ref<GLTFState> p_state, double *p_dst,
const GLTFBufferViewIndex p_buffer_view,
const int p_skip_every, const int p_skip_bytes,
const int p_element_size, const int p_count,
- const GLTFAccessorType p_accessor_type, const int p_component_count,
+ const GLTFAccessor::GLTFAccessorType p_accessor_type, const int p_component_count,
const int p_component_type, const int p_component_size,
const bool p_normalized, const int p_byte_offset,
const bool p_for_vertex);
@@ -266,7 +266,7 @@ private:
const Vector<Transform3D> p_attribs,
const bool p_for_vertex);
Error _encode_buffer_view(Ref<GLTFState> p_state, const double *p_src,
- const int p_count, const GLTFAccessorType p_accessor_type,
+ const int p_count, const GLTFAccessor::GLTFAccessorType p_accessor_type,
const int p_component_type, const bool p_normalized,
const int p_byte_offset, const bool p_for_vertex,
GLTFBufferViewIndex &r_accessor, const bool p_for_indices = false);
diff --git a/modules/gltf/structures/gltf_accessor.cpp b/modules/gltf/structures/gltf_accessor.cpp
index 602f0d9dc4..1ebc00a514 100644
--- a/modules/gltf/structures/gltf_accessor.cpp
+++ b/modules/gltf/structures/gltf_accessor.cpp
@@ -31,6 +31,14 @@
#include "gltf_accessor.h"
void GLTFAccessor::_bind_methods() {
+ BIND_ENUM_CONSTANT(TYPE_SCALAR);
+ BIND_ENUM_CONSTANT(TYPE_VEC2);
+ BIND_ENUM_CONSTANT(TYPE_VEC3);
+ BIND_ENUM_CONSTANT(TYPE_VEC4);
+ BIND_ENUM_CONSTANT(TYPE_MAT2);
+ BIND_ENUM_CONSTANT(TYPE_MAT3);
+ BIND_ENUM_CONSTANT(TYPE_MAT4);
+
ClassDB::bind_method(D_METHOD("get_buffer_view"), &GLTFAccessor::get_buffer_view);
ClassDB::bind_method(D_METHOD("set_buffer_view", "buffer_view"), &GLTFAccessor::set_buffer_view);
ClassDB::bind_method(D_METHOD("get_byte_offset"), &GLTFAccessor::get_byte_offset);
@@ -43,8 +51,8 @@ void GLTFAccessor::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_count", "count"), &GLTFAccessor::set_count);
ClassDB::bind_method(D_METHOD("get_accessor_type"), &GLTFAccessor::get_accessor_type);
ClassDB::bind_method(D_METHOD("set_accessor_type", "accessor_type"), &GLTFAccessor::set_accessor_type);
- ClassDB::bind_method(D_METHOD("get_type"), &GLTFAccessor::get_accessor_type);
- ClassDB::bind_method(D_METHOD("set_type", "type"), &GLTFAccessor::set_accessor_type);
+ ClassDB::bind_method(D_METHOD("get_type"), &GLTFAccessor::get_type);
+ ClassDB::bind_method(D_METHOD("set_type", "type"), &GLTFAccessor::set_type);
ClassDB::bind_method(D_METHOD("get_min"), &GLTFAccessor::get_min);
ClassDB::bind_method(D_METHOD("set_min", "min"), &GLTFAccessor::set_min);
ClassDB::bind_method(D_METHOD("get_max"), &GLTFAccessor::get_max);
@@ -67,8 +75,8 @@ void GLTFAccessor::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "component_type"), "set_component_type", "get_component_type"); // int
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "normalized"), "set_normalized", "get_normalized"); // bool
ADD_PROPERTY(PropertyInfo(Variant::INT, "count"), "set_count", "get_count"); // int
- ADD_PROPERTY(PropertyInfo(Variant::INT, "accessor_type"), "set_accessor_type", "get_accessor_type"); // GLTFAccessorType
- ADD_PROPERTY(PropertyInfo(Variant::INT, "type"), "set_type", "get_type"); // Deprecated, GLTFAccessorType
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "accessor_type"), "set_accessor_type", "get_accessor_type"); // GLTFAccessor::GLTFAccessorType
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "type", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_type", "get_type"); // Deprecated, int for GLTFAccessor::GLTFAccessorType
ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT64_ARRAY, "min"), "set_min", "get_min"); // Vector<real_t>
ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT64_ARRAY, "max"), "set_max", "get_max"); // Vector<real_t>
ADD_PROPERTY(PropertyInfo(Variant::INT, "sparse_count"), "set_sparse_count", "get_sparse_count"); // int
@@ -119,11 +127,19 @@ void GLTFAccessor::set_count(int p_count) {
count = p_count;
}
-int GLTFAccessor::get_accessor_type() {
+GLTFAccessor::GLTFAccessorType GLTFAccessor::get_accessor_type() {
+ return accessor_type;
+}
+
+void GLTFAccessor::set_accessor_type(GLTFAccessorType p_accessor_type) {
+ accessor_type = p_accessor_type;
+}
+
+int GLTFAccessor::get_type() {
return (int)accessor_type;
}
-void GLTFAccessor::set_accessor_type(int p_accessor_type) {
+void GLTFAccessor::set_type(int p_accessor_type) {
accessor_type = (GLTFAccessorType)p_accessor_type; // TODO: Register enum
}
diff --git a/modules/gltf/structures/gltf_accessor.h b/modules/gltf/structures/gltf_accessor.h
index 51ca282630..1a3a2cb494 100644
--- a/modules/gltf/structures/gltf_accessor.h
+++ b/modules/gltf/structures/gltf_accessor.h
@@ -35,20 +35,21 @@
#include "core/io/resource.h"
-enum GLTFAccessorType {
- TYPE_SCALAR,
- TYPE_VEC2,
- TYPE_VEC3,
- TYPE_VEC4,
- TYPE_MAT2,
- TYPE_MAT3,
- TYPE_MAT4,
-};
-
struct GLTFAccessor : public Resource {
GDCLASS(GLTFAccessor, Resource);
friend class GLTFDocument;
+public:
+ enum GLTFAccessorType {
+ TYPE_SCALAR,
+ TYPE_VEC2,
+ TYPE_VEC3,
+ TYPE_VEC4,
+ TYPE_MAT2,
+ TYPE_MAT3,
+ TYPE_MAT4,
+ };
+
private:
GLTFBufferViewIndex buffer_view = -1;
int byte_offset = 0;
@@ -84,8 +85,11 @@ public:
int get_count();
void set_count(int p_count);
- int get_accessor_type();
- void set_accessor_type(int p_accessor_type);
+ GLTFAccessorType get_accessor_type();
+ void set_accessor_type(GLTFAccessorType p_accessor_type);
+
+ int get_type();
+ void set_type(int p_accessor_type);
Vector<double> get_min();
void set_min(Vector<double> p_min);
@@ -112,4 +116,6 @@ public:
void set_sparse_values_byte_offset(int p_sparse_values_byte_offset);
};
+VARIANT_ENUM_CAST(GLTFAccessor::GLTFAccessorType);
+
#endif // GLTF_ACCESSOR_H
diff --git a/modules/jpg/image_loader_jpegd.cpp b/modules/jpg/image_loader_jpegd.cpp
index ada0cd01fa..53046de740 100644
--- a/modules/jpg/image_loader_jpegd.cpp
+++ b/modules/jpg/image_loader_jpegd.cpp
@@ -162,7 +162,7 @@ static Error _jpgd_save_to_output_stream(jpge::output_stream *p_output_stream, c
ERR_FAIL_COND_V_MSG(error != OK, error, "Couldn't decompress image.");
}
if (image->get_format() != Image::FORMAT_RGB8) {
- image = p_img->duplicate();
+ image = image->duplicate();
image->convert(Image::FORMAT_RGB8);
}
diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h
index 748ef3af94..81e9deb7ab 100644
--- a/modules/openxr/openxr_api.h
+++ b/modules/openxr/openxr_api.h
@@ -454,7 +454,6 @@ public:
_FORCE_INLINE_ XrTime get_predicted_display_time() { return frame_state.predictedDisplayTime; }
_FORCE_INLINE_ XrTime get_next_frame_time() { return frame_state.predictedDisplayTime + frame_state.predictedDisplayPeriod; }
_FORCE_INLINE_ bool can_render() {
- ERR_ON_RENDER_THREAD_V(false);
return instance != XR_NULL_HANDLE && session != XR_NULL_HANDLE && running && frame_state.shouldRender;
}
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 0c87199635..499ddb703b 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -5248,7 +5248,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) {
if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) {
- // Insert an additional space when cutting word bound for esthetics.
+ // Insert an additional space when cutting word bound for aesthetics.
if (cut_per_word && (ellipsis_pos > 0)) {
Glyph gl;
gl.count = 1;
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index 6cf6b236ed..b45c004011 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -4061,7 +4061,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) {
if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) {
- // Insert an additional space when cutting word bound for esthetics.
+ // Insert an additional space when cutting word bound for aesthetics.
if (cut_per_word && (ellipsis_pos > 0)) {
Glyph gl;
gl.count = 1;
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index 4abc6548bf..0cc929d226 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -24,6 +24,10 @@
android:hasFragileUserData="false"
android:requestLegacyExternalStorage="false"
tools:ignore="GoogleAppIndexingWarning" >
+ <profileable
+ android:shell="true"
+ android:enabled="true"
+ tools:targetApi="29" />
<!-- Records the version of the Godot editor used for building -->
<meta-data
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index 01759a1b2f..eb9ad9de05 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -7,7 +7,7 @@ ext.versions = [
targetSdk : 34,
buildTools : '34.0.0',
kotlinVersion : '1.9.20',
- fragmentVersion : '1.6.2',
+ fragmentVersion : '1.7.1',
nexusPublishVersion: '1.3.0',
javaVersion : JavaVersion.VERSION_17,
// Also update 'platform/android/detect.py#get_ndk_version()' when this is updated.
diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle
index 55fe2a22fe..37f68d295a 100644
--- a/platform/android/java/editor/build.gradle
+++ b/platform/android/java/editor/build.gradle
@@ -9,7 +9,7 @@ dependencies {
implementation "androidx.fragment:fragment:$versions.fragmentVersion"
implementation project(":lib")
- implementation "androidx.window:window:1.2.0"
+ implementation "androidx.window:window:1.3.0"
implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
}
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 5515347bd6..dad397de61 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
@@ -117,6 +117,10 @@ open class GodotEditor : GodotActivity() {
val longPressEnabled = enableLongPressGestures()
val panScaleEnabled = enablePanAndScaleGestures()
+ val useInputBuffering = useInputBuffering()
+ val useAccumulatedInput = useAccumulatedInput()
+ GodotLib.updateInputDispatchSettings(useAccumulatedInput, useInputBuffering)
+
checkForProjectPermissionsToEnable()
runOnUiThread {
@@ -124,6 +128,7 @@ open class GodotEditor : GodotActivity() {
godotFragment?.godot?.renderView?.inputHandler?.apply {
enableLongPress(longPressEnabled)
enablePanningAndScalingGestures(panScaleEnabled)
+ enableInputDispatchToRenderThread(!useInputBuffering && !useAccumulatedInput)
}
}
}
@@ -275,6 +280,13 @@ open class GodotEditor : GodotActivity() {
java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/enable_pan_and_scale_gestures"))
/**
+ * Use input buffering for the Godot Android editor.
+ */
+ protected open fun useInputBuffering() = java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/editor/android/use_input_buffering"))
+
+ protected open fun useAccumulatedInput() = java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/editor/android/use_accumulated_input"))
+
+ /**
* 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
*/
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 8e4e089211..f50b5577c3 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,8 @@
package org.godotengine.editor
+import org.godotengine.godot.GodotLib
+
/**
* Drives the 'run project' window of the Godot Editor.
*/
@@ -39,9 +41,13 @@ class GodotGame : GodotEditor() {
override fun overrideOrientationRequest() = false
- override fun enableLongPressGestures() = false
+ override fun enableLongPressGestures() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click"))
+
+ override fun enablePanAndScaleGestures() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures"))
+
+ override fun useInputBuffering() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_input_buffering"))
- override fun enablePanAndScaleGestures() = false
+ override fun useAccumulatedInput() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_accumulated_input"))
override fun checkForProjectPermissionsToEnable() {
// Nothing to do.. by the time we get here, the project permissions will have already
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 290be727ab..c188a97ca5 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -84,17 +84,20 @@ class Godot(private val context: Context) : SensorEventListener {
private companion object {
private val TAG = Godot::class.java.simpleName
- }
- private val windowManager: WindowManager by lazy {
- requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ // Supported build flavors
+ const val EDITOR_FLAVOR = "editor"
+ const val TEMPLATE_FLAVOR = "template"
}
+
+ private val windowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ private val mSensorManager: SensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
+ private val mClipboard: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+ private val vibratorService: Vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+
private val pluginRegistry: GodotPluginRegistry by lazy {
GodotPluginRegistry.getPluginRegistry()
}
- private val mSensorManager: SensorManager by lazy {
- requireActivity().getSystemService(Context.SENSOR_SERVICE) as SensorManager
- }
private val mAccelerometer: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
}
@@ -107,9 +110,6 @@ class Godot(private val context: Context) : SensorEventListener {
private val mGyroscope: Sensor? by lazy {
mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
}
- private val mClipboard: ClipboardManager by lazy {
- requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
- }
private val uiChangeListener = View.OnSystemUiVisibilityChangeListener { visibility: Int ->
if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) {
@@ -192,6 +192,8 @@ class Godot(private val context: Context) : SensorEventListener {
return
}
+ Log.v(TAG, "OnCreate: $primaryHost")
+
darkMode = context.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
beginBenchmarkMeasure("Startup", "Godot::onCreate")
@@ -200,6 +202,8 @@ class Godot(private val context: Context) : SensorEventListener {
val activity = requireActivity()
val window = activity.window
window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
+
+ Log.v(TAG, "Initializing Godot plugin registry")
GodotPluginRegistry.initializePluginRegistry(this, primaryHost.getHostPlugins(this))
if (io == null) {
io = GodotIO(activity)
@@ -323,13 +327,17 @@ class Godot(private val context: Context) : SensorEventListener {
return false
}
- if (expansionPackPath.isNotEmpty()) {
- commandLine.add("--main-pack")
- commandLine.add(expansionPackPath)
- }
- val activity = requireActivity()
- if (!nativeLayerInitializeCompleted) {
- nativeLayerInitializeCompleted = GodotLib.initialize(
+ Log.v(TAG, "OnInitNativeLayer: $host")
+
+ beginBenchmarkMeasure("Startup", "Godot::onInitNativeLayer")
+ try {
+ if (expansionPackPath.isNotEmpty()) {
+ commandLine.add("--main-pack")
+ commandLine.add(expansionPackPath)
+ }
+ val activity = requireActivity()
+ if (!nativeLayerInitializeCompleted) {
+ nativeLayerInitializeCompleted = GodotLib.initialize(
activity,
this,
activity.assets,
@@ -338,15 +346,20 @@ class Godot(private val context: Context) : SensorEventListener {
directoryAccessHandler,
fileAccessHandler,
useApkExpansion,
- )
- }
+ )
+ Log.v(TAG, "Godot native layer initialization completed: $nativeLayerInitializeCompleted")
+ }
- if (nativeLayerInitializeCompleted && !nativeLayerSetupCompleted) {
- nativeLayerSetupCompleted = GodotLib.setup(commandLine.toTypedArray(), tts)
- if (!nativeLayerSetupCompleted) {
- Log.e(TAG, "Unable to setup the Godot engine! Aborting...")
- alert(R.string.error_engine_setup_message, R.string.text_error_title, this::forceQuit)
+ if (nativeLayerInitializeCompleted && !nativeLayerSetupCompleted) {
+ nativeLayerSetupCompleted = GodotLib.setup(commandLine.toTypedArray(), tts)
+ if (!nativeLayerSetupCompleted) {
+ throw IllegalStateException("Unable to setup the Godot engine! Aborting...")
+ } else {
+ Log.v(TAG, "Godot native layer setup completed")
+ }
}
+ } finally {
+ endBenchmarkMeasure("Startup", "Godot::onInitNativeLayer")
}
return isNativeInitialized()
}
@@ -370,6 +383,9 @@ class Godot(private val context: Context) : SensorEventListener {
throw IllegalStateException("onInitNativeLayer() must be invoked successfully prior to initializing the render view")
}
+ Log.v(TAG, "OnInitRenderView: $host")
+
+ beginBenchmarkMeasure("Startup", "Godot::onInitRenderView")
try {
val activity: Activity = host.activity
containerLayout = providedContainerLayout
@@ -392,8 +408,7 @@ class Godot(private val context: Context) : SensorEventListener {
containerLayout?.addView(editText)
renderView = if (usesVulkan()) {
if (!meetsVulkanRequirements(activity.packageManager)) {
- alert(R.string.error_missing_vulkan_requirements_message, R.string.text_error_title, this::forceQuit)
- return null
+ throw IllegalStateException(activity.getString(R.string.error_missing_vulkan_requirements_message))
}
GodotVulkanRenderView(host, this)
} else {
@@ -482,11 +497,14 @@ class Godot(private val context: Context) : SensorEventListener {
containerLayout?.removeAllViews()
containerLayout = null
}
+
+ endBenchmarkMeasure("Startup", "Godot::onInitRenderView")
}
return containerLayout
}
fun onStart(host: GodotHost) {
+ Log.v(TAG, "OnStart: $host")
if (host != primaryHost) {
return
}
@@ -495,6 +513,7 @@ class Godot(private val context: Context) : SensorEventListener {
}
fun onResume(host: GodotHost) {
+ Log.v(TAG, "OnResume: $host")
if (host != primaryHost) {
return
}
@@ -527,6 +546,7 @@ class Godot(private val context: Context) : SensorEventListener {
}
fun onPause(host: GodotHost) {
+ Log.v(TAG, "OnPause: $host")
if (host != primaryHost) {
return
}
@@ -539,6 +559,7 @@ class Godot(private val context: Context) : SensorEventListener {
}
fun onStop(host: GodotHost) {
+ Log.v(TAG, "OnStop: $host")
if (host != primaryHost) {
return
}
@@ -547,6 +568,7 @@ class Godot(private val context: Context) : SensorEventListener {
}
fun onDestroy(primaryHost: GodotHost) {
+ Log.v(TAG, "OnDestroy: $primaryHost")
if (this.primaryHost != primaryHost) {
return
}
@@ -604,18 +626,29 @@ class Godot(private val context: Context) : SensorEventListener {
* Invoked on the render thread when the Godot setup is complete.
*/
private fun onGodotSetupCompleted() {
- Log.d(TAG, "OnGodotSetupCompleted")
-
- // These properties are defined after Godot setup completion, so we retrieve them here.
- val longPressEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click"))
- val panScaleEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures"))
- val rotaryInputAxis = java.lang.Integer.parseInt(GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis"))
-
- runOnUiThread {
- renderView?.inputHandler?.apply {
- enableLongPress(longPressEnabled)
- enablePanningAndScalingGestures(panScaleEnabled)
- setRotaryInputAxis(rotaryInputAxis)
+ Log.v(TAG, "OnGodotSetupCompleted")
+
+ if (!isEditorBuild()) {
+ // These properties are defined after Godot setup completion, so we retrieve them here.
+ val longPressEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click"))
+ val panScaleEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures"))
+ val rotaryInputAxisValue = GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis")
+
+ val useInputBuffering = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_input_buffering"))
+ val useAccumulatedInput = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_accumulated_input"))
+ GodotLib.updateInputDispatchSettings(useAccumulatedInput, useInputBuffering)
+
+ runOnUiThread {
+ renderView?.inputHandler?.apply {
+ enableLongPress(longPressEnabled)
+ enablePanningAndScalingGestures(panScaleEnabled)
+ enableInputDispatchToRenderThread(!useInputBuffering && !useAccumulatedInput)
+ try {
+ setRotaryInputAxis(Integer.parseInt(rotaryInputAxisValue))
+ } catch (e: NumberFormatException) {
+ Log.w(TAG, e)
+ }
+ }
}
}
@@ -629,7 +662,7 @@ class Godot(private val context: Context) : SensorEventListener {
* Invoked on the render thread when the Godot main loop has started.
*/
private fun onGodotMainLoopStarted() {
- Log.d(TAG, "OnGodotMainLoopStarted")
+ Log.v(TAG, "OnGodotMainLoopStarted")
for (plugin in pluginRegistry.allPlugins) {
plugin.onGodotMainLoopStarted()
@@ -646,12 +679,7 @@ class Godot(private val context: Context) : SensorEventListener {
decorView.setOnSystemUiVisibilityChangeListener(uiChangeListener)
}
- @Keep
- private fun alert(message: String, title: String) {
- alert(message, title, null)
- }
-
- private fun alert(
+ fun alert(
@StringRes messageResId: Int,
@StringRes titleResId: Int,
okCallback: Runnable?
@@ -660,7 +688,9 @@ class Godot(private val context: Context) : SensorEventListener {
alert(res.getString(messageResId), res.getString(titleResId), okCallback)
}
- private fun alert(message: String, title: String, okCallback: Runnable?) {
+ @JvmOverloads
+ @Keep
+ fun alert(message: String, title: String, okCallback: Runnable? = null) {
val activity: Activity = getActivity() ?: return
runOnUiThread {
val builder = AlertDialog.Builder(activity)
@@ -759,6 +789,11 @@ class Godot(private val context: Context) : SensorEventListener {
return mClipboard.hasPrimaryClip()
}
+ /**
+ * @return true if this is an editor build, false if this is a template build
+ */
+ fun isEditorBuild() = BuildConfig.FLAVOR == EDITOR_FLAVOR
+
fun getClipboard(): String {
val clipData = mClipboard.primaryClip ?: return ""
val text = clipData.getItemAt(0).text ?: return ""
@@ -770,7 +805,7 @@ class Godot(private val context: Context) : SensorEventListener {
mClipboard.setPrimaryClip(clip)
}
- private fun forceQuit() {
+ fun forceQuit() {
forceQuit(0)
}
@@ -881,7 +916,6 @@ class Godot(private val context: Context) : SensorEventListener {
@Keep
private fun vibrate(durationMs: Int, amplitude: Int) {
if (durationMs > 0 && requestPermission("VIBRATE")) {
- val vibratorService = getActivity()?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator? ?: return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (amplitude <= -1) {
vibratorService.vibrate(
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
index a323045e1b..1612ddd0b3 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java
@@ -42,6 +42,7 @@ import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.os.Messenger;
+import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -203,6 +204,12 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH
if (godotContainerLayout == null) {
throw new IllegalStateException("Unable to initialize engine render view");
}
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Engine initialization failed", e);
+ final String errorMessage = TextUtils.isEmpty(e.getMessage())
+ ? getString(R.string.error_engine_setup_message)
+ : e.getMessage();
+ godot.alert(errorMessage, getString(R.string.text_error_title), godot::forceQuit);
} catch (IllegalArgumentException ignored) {
final Activity activity = getActivity();
Intent notifierIntent = new Intent(activity, activity.getClass());
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
index 4b51bd778d..219631284a 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
@@ -121,7 +121,7 @@ public class GodotIO {
activity.startActivity(intent);
return 0;
- } catch (ActivityNotFoundException e) {
+ } catch (Exception e) {
Log.e(TAG, "Unable to open uri " + uriString, e);
return 1;
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
index d0c3d4a687..37e889daf7 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
@@ -240,4 +240,11 @@ public class GodotLib {
* @see GodotRenderer#onActivityPaused()
*/
public static native void onRendererPaused();
+
+ /**
+ * Invoked on the GL thread to update the input dispatch settings
+ * @param useAccumulatedInput True to use accumulated input, false otherwise
+ * @param useInputBuffering True to use input buffering, false otherwise
+ */
+ public static native void updateInputDispatchSettings(boolean useAccumulatedInput, boolean useInputBuffering);
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java b/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java
index c316812404..c9421a3257 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java
@@ -1704,15 +1704,6 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
mHasSurface = true;
mFinishedCreatingEglSurface = false;
sGLThreadManager.notifyAll();
- while (mWaitingForSurface
- && !mFinishedCreatingEglSurface
- && !mExited) {
- try {
- sGLThreadManager.wait();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
}
}
@@ -1723,13 +1714,6 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
}
mHasSurface = false;
sGLThreadManager.notifyAll();
- while((!mWaitingForSurface) && (!mExited)) {
- try {
- sGLThreadManager.wait();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
}
}
@@ -1740,16 +1724,6 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
}
mRequestPaused = true;
sGLThreadManager.notifyAll();
- while ((! mExited) && (! mPaused)) {
- if (LOG_PAUSE_RESUME) {
- Log.i("Main thread", "onPause waiting for mPaused.");
- }
- try {
- sGLThreadManager.wait();
- } catch (InterruptedException ex) {
- Thread.currentThread().interrupt();
- }
- }
}
}
@@ -1762,16 +1736,6 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
mRequestRender = true;
mRenderComplete = false;
sGLThreadManager.notifyAll();
- while ((! mExited) && mPaused && (!mRenderComplete)) {
- if (LOG_PAUSE_RESUME) {
- Log.i("Main thread", "onResume waiting for !mPaused.");
- }
- try {
- sGLThreadManager.wait();
- } catch (InterruptedException ex) {
- Thread.currentThread().interrupt();
- }
- }
}
}
@@ -1793,19 +1757,6 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
}
sGLThreadManager.notifyAll();
-
- // Wait for thread to react to resize and render a frame
- while (! mExited && !mPaused && !mRenderComplete
- && ableToDraw()) {
- if (LOG_SURFACE) {
- Log.i("Main thread", "onWindowResize waiting for render complete from tid=" + getId());
- }
- try {
- sGLThreadManager.wait();
- } catch (InterruptedException ex) {
- Thread.currentThread().interrupt();
- }
- }
}
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
index 49b34a5229..4cd3bd8db9 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt
@@ -44,7 +44,7 @@ import org.godotengine.godot.GodotLib
* @See https://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener
* @See https://developer.android.com/reference/android/view/ScaleGestureDetector.OnScaleGestureListener
*/
-internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureListener {
+internal class GodotGestureHandler(private val inputHandler: GodotInputHandler) : SimpleOnGestureListener(), OnScaleGestureListener {
companion object {
private val TAG = GodotGestureHandler::class.java.simpleName
@@ -65,13 +65,13 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
private var lastDragY: Float = 0.0f
override fun onDown(event: MotionEvent): Boolean {
- GodotInputHandler.handleMotionEvent(event, MotionEvent.ACTION_DOWN, nextDownIsDoubleTap)
+ inputHandler.handleMotionEvent(event, MotionEvent.ACTION_DOWN, nextDownIsDoubleTap)
nextDownIsDoubleTap = false
return true
}
override fun onSingleTapUp(event: MotionEvent): Boolean {
- GodotInputHandler.handleMotionEvent(event)
+ inputHandler.handleMotionEvent(event)
return true
}
@@ -85,10 +85,10 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
}
// Cancel the previous down event
- GodotInputHandler.handleMotionEvent(event, MotionEvent.ACTION_CANCEL)
+ inputHandler.handleMotionEvent(event, MotionEvent.ACTION_CANCEL)
// Turn a context click into a single tap right mouse button click.
- GodotInputHandler.handleMouseEvent(
+ inputHandler.handleMouseEvent(
event,
MotionEvent.ACTION_DOWN,
MotionEvent.BUTTON_SECONDARY,
@@ -104,7 +104,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
if (!hasCapture) {
// Dispatch a mouse relative ACTION_UP event to signal the end of the capture
- GodotInputHandler.handleMouseEvent(MotionEvent.ACTION_UP, true)
+ inputHandler.handleMouseEvent(MotionEvent.ACTION_UP, true)
}
pointerCaptureInProgress = hasCapture
}
@@ -131,9 +131,9 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
if (contextClickInProgress || GodotInputHandler.isMouseEvent(event)) {
// This may be an ACTION_BUTTON_RELEASE event which we don't handle,
// so we convert it to an ACTION_UP event.
- GodotInputHandler.handleMouseEvent(event, MotionEvent.ACTION_UP)
+ inputHandler.handleMouseEvent(event, MotionEvent.ACTION_UP)
} else {
- GodotInputHandler.handleTouchEvent(event)
+ inputHandler.handleTouchEvent(event)
}
pointerCaptureInProgress = false
dragInProgress = false
@@ -148,7 +148,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
private fun onActionMove(event: MotionEvent): Boolean {
if (contextClickInProgress) {
- GodotInputHandler.handleMouseEvent(event, event.actionMasked, MotionEvent.BUTTON_SECONDARY, false)
+ inputHandler.handleMouseEvent(event, event.actionMasked, MotionEvent.BUTTON_SECONDARY, false)
return true
} else if (!scaleInProgress) {
// The 'onScroll' event is triggered with a long delay.
@@ -158,7 +158,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
if (lastDragX != event.getX(0) || lastDragY != event.getY(0)) {
lastDragX = event.getX(0)
lastDragY = event.getY(0)
- GodotInputHandler.handleMotionEvent(event)
+ inputHandler.handleMotionEvent(event)
return true
}
}
@@ -168,9 +168,9 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
override fun onDoubleTapEvent(event: MotionEvent): Boolean {
if (event.actionMasked == MotionEvent.ACTION_UP) {
nextDownIsDoubleTap = false
- GodotInputHandler.handleMotionEvent(event)
+ inputHandler.handleMotionEvent(event)
} else if (event.actionMasked == MotionEvent.ACTION_MOVE && !panningAndScalingEnabled) {
- GodotInputHandler.handleMotionEvent(event)
+ inputHandler.handleMotionEvent(event)
}
return true
@@ -191,7 +191,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
if (dragInProgress || lastDragX != 0.0f || lastDragY != 0.0f) {
if (originEvent != null) {
// Cancel the drag
- GodotInputHandler.handleMotionEvent(originEvent, MotionEvent.ACTION_CANCEL)
+ inputHandler.handleMotionEvent(originEvent, MotionEvent.ACTION_CANCEL)
}
dragInProgress = false
lastDragX = 0.0f
@@ -202,12 +202,12 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
val x = terminusEvent.x
val y = terminusEvent.y
if (terminusEvent.pointerCount >= 2 && panningAndScalingEnabled && !pointerCaptureInProgress && !dragInProgress) {
- GodotLib.pan(x, y, distanceX / 5f, distanceY / 5f)
+ inputHandler.handlePanEvent(x, y, distanceX / 5f, distanceY / 5f)
} else if (!scaleInProgress) {
dragInProgress = true
lastDragX = terminusEvent.getX(0)
lastDragY = terminusEvent.getY(0)
- GodotInputHandler.handleMotionEvent(terminusEvent)
+ inputHandler.handleMotionEvent(terminusEvent)
}
return true
}
@@ -218,11 +218,7 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi
}
if (detector.scaleFactor >= 0.8f && detector.scaleFactor != 1f && detector.scaleFactor <= 1.2f) {
- GodotLib.magnify(
- detector.focusX,
- detector.focusY,
- detector.scaleFactor
- )
+ inputHandler.handleMagnifyEvent(detector.focusX, detector.focusY, detector.scaleFactor)
}
return true
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
index 83e76e49c9..889618914d 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java
@@ -75,7 +75,9 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
*/
private int lastSeenToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
- private static int rotaryInputAxis = ROTARY_INPUT_VERTICAL_AXIS;
+ private int rotaryInputAxis = ROTARY_INPUT_VERTICAL_AXIS;
+
+ private boolean dispatchInputToRenderThread = false;
public GodotInputHandler(GodotRenderView godotView) {
final Context context = godotView.getView().getContext();
@@ -83,7 +85,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
mInputManager = (InputManager)context.getSystemService(Context.INPUT_SERVICE);
mInputManager.registerInputDeviceListener(this, null);
- this.godotGestureHandler = new GodotGestureHandler();
+ this.godotGestureHandler = new GodotGestureHandler(this);
this.gestureDetector = new GestureDetector(context, godotGestureHandler);
this.gestureDetector.setIsLongpressEnabled(false);
this.scaleGestureDetector = new ScaleGestureDetector(context, godotGestureHandler);
@@ -109,6 +111,22 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
/**
+ * Specifies whether input should be dispatch on the UI thread or on the Render thread.
+ * @param enable true to dispatch input on the Render thread, false to dispatch input on the UI thread
+ */
+ public void enableInputDispatchToRenderThread(boolean enable) {
+ this.dispatchInputToRenderThread = enable;
+ }
+
+ /**
+ * @return true if input must be dispatched from the render thread. If false, input is
+ * dispatched from the UI thread.
+ */
+ private boolean shouldDispatchInputToRenderThread() {
+ return dispatchInputToRenderThread;
+ }
+
+ /**
* On Wear OS devices, sets which axis of the mouse wheel rotary input is mapped to. This is 1 (vertical axis) by default.
*/
public void setRotaryInputAxis(int axis) {
@@ -151,14 +169,14 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
if (mJoystickIds.indexOfKey(deviceId) >= 0) {
final int button = getGodotButton(keyCode);
final int godotJoyId = mJoystickIds.get(deviceId);
- GodotLib.joybutton(godotJoyId, button, false);
+ handleJoystickButtonEvent(godotJoyId, button, false);
}
} else {
// getKeyCode(): The physical key that was pressed.
final int physical_keycode = event.getKeyCode();
final int unicode = event.getUnicodeChar();
final int key_label = event.getDisplayLabel();
- GodotLib.key(physical_keycode, unicode, key_label, false, event.getRepeatCount() > 0);
+ handleKeyEvent(physical_keycode, unicode, key_label, false, event.getRepeatCount() > 0);
};
return true;
@@ -187,13 +205,13 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
if (mJoystickIds.indexOfKey(deviceId) >= 0) {
final int button = getGodotButton(keyCode);
final int godotJoyId = mJoystickIds.get(deviceId);
- GodotLib.joybutton(godotJoyId, button, true);
+ handleJoystickButtonEvent(godotJoyId, button, true);
}
} else {
final int physical_keycode = event.getKeyCode();
final int unicode = event.getUnicodeChar();
final int key_label = event.getDisplayLabel();
- GodotLib.key(physical_keycode, unicode, key_label, true, event.getRepeatCount() > 0);
+ handleKeyEvent(physical_keycode, unicode, key_label, true, event.getRepeatCount() > 0);
}
return true;
@@ -248,7 +266,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
if (joystick.axesValues.indexOfKey(axis) < 0 || (float)joystick.axesValues.get(axis) != value) {
// save value to prevent repeats
joystick.axesValues.put(axis, value);
- GodotLib.joyaxis(godotJoyId, i, value);
+ handleJoystickAxisEvent(godotJoyId, i, value);
}
}
@@ -258,7 +276,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
if (joystick.hatX != hatX || joystick.hatY != hatY) {
joystick.hatX = hatX;
joystick.hatY = hatY;
- GodotLib.joyhat(godotJoyId, hatX, hatY);
+ handleJoystickHatEvent(godotJoyId, hatX, hatY);
}
}
return true;
@@ -284,10 +302,12 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
int[] deviceIds = mInputManager.getInputDeviceIds();
for (int deviceId : deviceIds) {
InputDevice device = mInputManager.getInputDevice(deviceId);
- if (DEBUG) {
- Log.v(TAG, String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName()));
+ if (device != null) {
+ if (DEBUG) {
+ Log.v(TAG, String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName()));
+ }
+ onInputDeviceAdded(deviceId);
}
- onInputDeviceAdded(deviceId);
}
}
@@ -364,7 +384,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
mJoysticksDevices.put(deviceId, joystick);
- GodotLib.joyconnectionchanged(id, true, joystick.name);
+ handleJoystickConnectionChangedEvent(id, true, joystick.name);
}
@Override
@@ -378,7 +398,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
final int godotJoyId = mJoystickIds.get(deviceId);
mJoystickIds.delete(deviceId);
mJoysticksDevices.delete(deviceId);
- GodotLib.joyconnectionchanged(godotJoyId, false, "");
+ handleJoystickConnectionChangedEvent(godotJoyId, false, "");
}
@Override
@@ -482,15 +502,15 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
}
}
- static boolean handleMotionEvent(final MotionEvent event) {
+ boolean handleMotionEvent(final MotionEvent event) {
return handleMotionEvent(event, event.getActionMasked());
}
- static boolean handleMotionEvent(final MotionEvent event, int eventActionOverride) {
+ boolean handleMotionEvent(final MotionEvent event, int eventActionOverride) {
return handleMotionEvent(event, eventActionOverride, false);
}
- static boolean handleMotionEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) {
+ boolean handleMotionEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) {
if (isMouseEvent(event)) {
return handleMouseEvent(event, eventActionOverride, doubleTap);
}
@@ -523,19 +543,19 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
return (float)Math.cos(orientation) * tiltMult;
}
- static boolean handleMouseEvent(final MotionEvent event) {
+ boolean handleMouseEvent(final MotionEvent event) {
return handleMouseEvent(event, event.getActionMasked());
}
- static boolean handleMouseEvent(final MotionEvent event, int eventActionOverride) {
+ boolean handleMouseEvent(final MotionEvent event, int eventActionOverride) {
return handleMouseEvent(event, eventActionOverride, false);
}
- static boolean handleMouseEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) {
+ boolean handleMouseEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) {
return handleMouseEvent(event, eventActionOverride, event.getButtonState(), doubleTap);
}
- static boolean handleMouseEvent(final MotionEvent event, int eventActionOverride, int buttonMaskOverride, boolean doubleTap) {
+ boolean handleMouseEvent(final MotionEvent event, int eventActionOverride, int buttonMaskOverride, boolean doubleTap) {
final float x = event.getX();
final float y = event.getY();
@@ -564,11 +584,11 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
return handleMouseEvent(eventActionOverride, buttonMaskOverride, x, y, horizontalFactor, verticalFactor, doubleTap, sourceMouseRelative, pressure, getEventTiltX(event), getEventTiltY(event));
}
- static boolean handleMouseEvent(int eventAction, boolean sourceMouseRelative) {
+ boolean handleMouseEvent(int eventAction, boolean sourceMouseRelative) {
return handleMouseEvent(eventAction, 0, 0f, 0f, 0f, 0f, false, sourceMouseRelative, 1f, 0f, 0f);
}
- static boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative, float pressure, float tiltX, float tiltY) {
+ boolean handleMouseEvent(int eventAction, int buttonsMask, float x, float y, float deltaX, float deltaY, boolean doubleClick, boolean sourceMouseRelative, float pressure, float tiltX, float tiltY) {
// Fix the buttonsMask
switch (eventAction) {
case MotionEvent.ACTION_CANCEL:
@@ -584,6 +604,7 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
break;
}
+ final int updatedButtonsMask = buttonsMask;
// We don't handle ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE events as they typically
// follow ACTION_DOWN and ACTION_UP events. As such, handling them would result in duplicate
// stream of events to the engine.
@@ -596,22 +617,26 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
case MotionEvent.ACTION_HOVER_MOVE:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_SCROLL: {
- GodotLib.dispatchMouseEvent(eventAction, buttonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, pressure, tiltX, tiltY);
+ if (shouldDispatchInputToRenderThread()) {
+ mRenderView.queueOnRenderThread(() -> GodotLib.dispatchMouseEvent(eventAction, updatedButtonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, pressure, tiltX, tiltY));
+ } else {
+ GodotLib.dispatchMouseEvent(eventAction, updatedButtonsMask, x, y, deltaX, deltaY, doubleClick, sourceMouseRelative, pressure, tiltX, tiltY);
+ }
return true;
}
}
return false;
}
- static boolean handleTouchEvent(final MotionEvent event) {
+ boolean handleTouchEvent(final MotionEvent event) {
return handleTouchEvent(event, event.getActionMasked());
}
- static boolean handleTouchEvent(final MotionEvent event, int eventActionOverride) {
+ boolean handleTouchEvent(final MotionEvent event, int eventActionOverride) {
return handleTouchEvent(event, eventActionOverride, false);
}
- static boolean handleTouchEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) {
+ boolean handleTouchEvent(final MotionEvent event, int eventActionOverride, boolean doubleTap) {
final int pointerCount = event.getPointerCount();
if (pointerCount == 0) {
return true;
@@ -636,10 +661,70 @@ public class GodotInputHandler implements InputManager.InputDeviceListener {
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_POINTER_DOWN: {
- GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap);
+ if (shouldDispatchInputToRenderThread()) {
+ mRenderView.queueOnRenderThread(() -> GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap));
+ } else {
+ GodotLib.dispatchTouchEvent(eventActionOverride, actionPointerId, pointerCount, positions, doubleTap);
+ }
return true;
}
}
return false;
}
+
+ void handleMagnifyEvent(float x, float y, float factor) {
+ if (shouldDispatchInputToRenderThread()) {
+ mRenderView.queueOnRenderThread(() -> GodotLib.magnify(x, y, factor));
+ } else {
+ GodotLib.magnify(x, y, factor);
+ }
+ }
+
+ void handlePanEvent(float x, float y, float deltaX, float deltaY) {
+ if (shouldDispatchInputToRenderThread()) {
+ mRenderView.queueOnRenderThread(() -> GodotLib.pan(x, y, deltaX, deltaY));
+ } else {
+ GodotLib.pan(x, y, deltaX, deltaY);
+ }
+ }
+
+ private void handleJoystickButtonEvent(int device, int button, boolean pressed) {
+ if (shouldDispatchInputToRenderThread()) {
+ mRenderView.queueOnRenderThread(() -> GodotLib.joybutton(device, button, pressed));
+ } else {
+ GodotLib.joybutton(device, button, pressed);
+ }
+ }
+
+ private void handleJoystickAxisEvent(int device, int axis, float value) {
+ if (shouldDispatchInputToRenderThread()) {
+ mRenderView.queueOnRenderThread(() -> GodotLib.joyaxis(device, axis, value));
+ } else {
+ GodotLib.joyaxis(device, axis, value);
+ }
+ }
+
+ private void handleJoystickHatEvent(int device, int hatX, int hatY) {
+ if (shouldDispatchInputToRenderThread()) {
+ mRenderView.queueOnRenderThread(() -> GodotLib.joyhat(device, hatX, hatY));
+ } else {
+ GodotLib.joyhat(device, hatX, hatY);
+ }
+ }
+
+ private void handleJoystickConnectionChangedEvent(int device, boolean connected, String name) {
+ if (shouldDispatchInputToRenderThread()) {
+ mRenderView.queueOnRenderThread(() -> GodotLib.joyconnectionchanged(device, connected, name));
+ } else {
+ GodotLib.joyconnectionchanged(device, connected, name);
+ }
+ }
+
+ void handleKeyEvent(int physicalKeycode, int unicode, int keyLabel, boolean pressed, boolean echo) {
+ if (shouldDispatchInputToRenderThread()) {
+ mRenderView.queueOnRenderThread(() -> GodotLib.key(physicalKeycode, unicode, keyLabel, pressed, echo));
+ } else {
+ GodotLib.key(physicalKeycode, unicode, keyLabel, pressed, echo);
+ }
+ }
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
index 06b565c30f..e545669970 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
@@ -93,8 +93,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
@Override
public void beforeTextChanged(final CharSequence pCharSequence, final int start, final int count, final int after) {
for (int i = 0; i < count; ++i) {
- GodotLib.key(KeyEvent.KEYCODE_DEL, 0, 0, true, false);
- GodotLib.key(KeyEvent.KEYCODE_DEL, 0, 0, false, false);
+ mRenderView.getInputHandler().handleKeyEvent(KeyEvent.KEYCODE_DEL, 0, 0, true, false);
+ mRenderView.getInputHandler().handleKeyEvent(KeyEvent.KEYCODE_DEL, 0, 0, false, false);
if (mHasSelection) {
mHasSelection = false;
@@ -115,8 +115,8 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
// Return keys are handled through action events
continue;
}
- GodotLib.key(0, character, 0, true, false);
- GodotLib.key(0, character, 0, false, false);
+ mRenderView.getInputHandler().handleKeyEvent(0, character, 0, true, false);
+ mRenderView.getInputHandler().handleKeyEvent(0, character, 0, false, false);
}
}
@@ -127,18 +127,16 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
if (characters != null) {
for (int i = 0; i < characters.length(); i++) {
final int character = characters.codePointAt(i);
- GodotLib.key(0, character, 0, true, false);
- GodotLib.key(0, character, 0, false, false);
+ mRenderView.getInputHandler().handleKeyEvent(0, character, 0, true, false);
+ mRenderView.getInputHandler().handleKeyEvent(0, character, 0, false, false);
}
}
}
if (pActionID == EditorInfo.IME_ACTION_DONE) {
// Enter key has been pressed
- mRenderView.queueOnRenderThread(() -> {
- GodotLib.key(KeyEvent.KEYCODE_ENTER, 0, 0, true, false);
- GodotLib.key(KeyEvent.KEYCODE_ENTER, 0, 0, false, false);
- });
+ mRenderView.getInputHandler().handleKeyEvent(KeyEvent.KEYCODE_ENTER, 0, 0, true, false);
+ mRenderView.getInputHandler().handleKeyEvent(KeyEvent.KEYCODE_ENTER, 0, 0, false, false);
mRenderView.getView().requestFocus();
return true;
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt
index 69748c0a8d..d39f2309b8 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt
@@ -81,7 +81,8 @@ fun beginBenchmarkMeasure(scope: String, label: String) {
*
* * Note: Only enabled on 'editorDev' build variant.
*/
-fun endBenchmarkMeasure(scope: String, label: String) {
+@JvmOverloads
+fun endBenchmarkMeasure(scope: String, label: String, dumpBenchmark: Boolean = false) {
if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") {
return
}
@@ -93,6 +94,10 @@ fun endBenchmarkMeasure(scope: String, label: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Trace.endAsyncSection("[$scope] $label", 0)
}
+
+ if (dumpBenchmark) {
+ dumpBenchmark()
+ }
}
/**
@@ -102,11 +107,11 @@ fun endBenchmarkMeasure(scope: String, label: String) {
* * Note: Only enabled on 'editorDev' build variant.
*/
@JvmOverloads
-fun dumpBenchmark(fileAccessHandler: FileAccessHandler?, filepath: String? = benchmarkFile) {
+fun dumpBenchmark(fileAccessHandler: FileAccessHandler? = null, filepath: String? = benchmarkFile) {
if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") {
return
}
- if (!useBenchmark) {
+ if (!useBenchmark || benchmarkTracker.isEmpty()) {
return
}
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 8493a8e932..87d4281c5a 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -549,4 +549,11 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIE
os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED);
}
}
+
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_updateInputDispatchSettings(JNIEnv *env, jclass clazz, jboolean p_use_accumulated_input, jboolean p_use_input_buffering) {
+ if (Input::get_singleton()) {
+ Input::get_singleton()->set_use_accumulated_input(p_use_accumulated_input);
+ Input::get_singleton()->set_use_input_buffering(p_use_input_buffering);
+ }
+}
}
diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h
index f32ffc291a..852c475e7e 100644
--- a/platform/android/java_godot_lib_jni.h
+++ b/platform/android/java_godot_lib_jni.h
@@ -69,6 +69,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResu
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_updateInputDispatchSettings(JNIEnv *env, jclass clazz, jboolean p_use_accumulated_input, jboolean p_use_input_buffering);
}
#endif // JAVA_GODOT_LIB_JNI_H
diff --git a/platform/ios/keyboard_input_view.mm b/platform/ios/keyboard_input_view.mm
index 8b614662b7..4067701a41 100644
--- a/platform/ios/keyboard_input_view.mm
+++ b/platform/ios/keyboard_input_view.mm
@@ -149,23 +149,18 @@
return;
}
+ NSString *substringToDelete = nil;
if (self.previousSelectedRange.length == 0) {
- // We are deleting all text before cursor if no range was selected.
- // This way any inserted or changed text will be updated.
- NSString *substringToDelete = [self.previousText substringToIndex:self.previousSelectedRange.location];
- [self deleteText:substringToDelete.length];
+ // Get previous text to delete.
+ substringToDelete = [self.previousText substringToIndex:self.previousSelectedRange.location];
} else {
- // If text was previously selected
- // we are sending only one `backspace`.
- // It will remove all text from text input.
+ // If text was previously selected we are sending only one `backspace`. It will remove all text from text input.
[self deleteText:1];
}
- NSString *substringToEnter;
-
+ NSString *substringToEnter = nil;
if (self.selectedRange.length == 0) {
- // If previous cursor had a selection
- // we have to calculate an inserted text.
+ // If previous cursor had a selection we have to calculate an inserted text.
if (self.previousSelectedRange.length != 0) {
NSInteger rangeEnd = self.selectedRange.location + self.selectedRange.length;
NSInteger rangeStart = MIN(self.previousSelectedRange.location, self.selectedRange.location);
@@ -187,7 +182,18 @@
substringToEnter = [self.text substringWithRange:self.selectedRange];
}
- [self enterText:substringToEnter];
+ NSInteger skip = 0;
+ if (substringToDelete != nil) {
+ for (NSInteger i = 0; i < MIN([substringToDelete length], [substringToEnter length]); i++) {
+ if ([substringToDelete characterAtIndex:i] == [substringToEnter characterAtIndex:i]) {
+ skip++;
+ } else {
+ break;
+ }
+ }
+ [self deleteText:[substringToDelete length] - skip]; // Delete changed part of previous text.
+ }
+ [self enterText:[substringToEnter substringFromIndex:skip]]; // Enter changed part of new text.
self.previousText = self.text;
self.previousSelectedRange = self.selectedRange;
diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp
index 3534c1afee..a67428b9a4 100644
--- a/platform/linuxbsd/joypad_linux.cpp
+++ b/platform/linuxbsd/joypad_linux.cpp
@@ -374,6 +374,12 @@ void JoypadLinux::open_joypad(const char *p_path) {
name = namebuf;
}
+ for (const String &word : name.to_lower().split(" ")) {
+ if (banned_words.has(word)) {
+ return;
+ }
+ }
+
if (ioctl(fd, EVIOCGID, &inpid) < 0) {
close(fd);
return;
diff --git a/platform/linuxbsd/joypad_linux.h b/platform/linuxbsd/joypad_linux.h
index 26a9908d4e..bf24d8e5a5 100644
--- a/platform/linuxbsd/joypad_linux.h
+++ b/platform/linuxbsd/joypad_linux.h
@@ -94,6 +94,21 @@ private:
Vector<String> attached_devices;
+ // List of lowercase words that will prevent the controller from being recognized if its name matches.
+ // This is done to prevent trackpads, graphics tablets and motherboard LED controllers from being
+ // recognized as controllers (and taking up controller ID slots as a result).
+ // Only whole words are matched within the controller name string. The match is case-insensitive.
+ const Vector<String> banned_words = {
+ "touchpad", // Matches e.g. "SynPS/2 Synaptics TouchPad", "Sony Interactive Entertainment DualSense Wireless Controller Touchpad"
+ "trackpad",
+ "clickpad",
+ "keyboard", // Matches e.g. "PG-90215 Keyboard", "Usb Keyboard Usb Keyboard Consumer Control"
+ "mouse", // Matches e.g. "Mouse passthrough"
+ "pen", // Matches e.g. "Wacom One by Wacom S Pen"
+ "finger", // Matches e.g. "Wacom HID 495F Finger"
+ "led", // Matches e.g. "ASRock LED Controller"
+ };
+
static void monitor_joypads_thread_func(void *p_user);
void monitor_joypads_thread_run();
diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp
index 341cc517e3..7bdc75db29 100644
--- a/platform/linuxbsd/wayland/wayland_thread.cpp
+++ b/platform/linuxbsd/wayland/wayland_thread.cpp
@@ -2049,9 +2049,14 @@ void WaylandThread::_wp_relative_pointer_on_relative_motion(void *data, struct z
PointerData &pd = ss->pointer_data_buffer;
+ WindowState *ws = wl_surface_get_window_state(ss->pointed_surface);
+ ERR_FAIL_NULL(ws);
+
pd.relative_motion.x = wl_fixed_to_double(dx);
pd.relative_motion.y = wl_fixed_to_double(dy);
+ pd.relative_motion *= window_state_get_scale_factor(ws);
+
pd.relative_motion_time = uptime_lo;
}
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index a1a91345ac..da45391995 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -568,23 +568,7 @@ void DisplayServerMacOS::menu_callback(id p_sender) {
}
GodotMenuItem *value = [p_sender representedObject];
-
if (value) {
- if (value->max_states > 0) {
- value->state++;
- if (value->state >= value->max_states) {
- value->state = 0;
- }
- }
-
- if (value->checkable_type == CHECKABLE_TYPE_CHECK_BOX) {
- if ([p_sender state] == NSControlStateValueOff) {
- [p_sender setState:NSControlStateValueOn];
- } else {
- [p_sender setState:NSControlStateValueOff];
- }
- }
-
if (value->callback.is_valid()) {
MenuCall mc;
mc.tag = value->meta;
diff --git a/platform/macos/godot_main_macos.mm b/platform/macos/godot_main_macos.mm
index 942c351ac0..eebaed0eaf 100644
--- a/platform/macos/godot_main_macos.mm
+++ b/platform/macos/godot_main_macos.mm
@@ -41,8 +41,8 @@
int main(int argc, char **argv) {
#if defined(VULKAN_ENABLED)
- // MoltenVK - enable full component swizzling support.
- setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
+ setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1); // MoltenVK - enable full component swizzling support.
+ setenv("MVK_CONFIG_SWAPCHAIN_MIN_MAG_FILTER_USE_NEAREST", "0", 1); // MoltenVK - use linear surface scaling. TODO: remove when full DPI scaling is implemented.
#endif
#if defined(SANITIZERS_ENABLED)
diff --git a/platform/macos/godot_menu_item.h b/platform/macos/godot_menu_item.h
index b6e2d41c08..e1af317259 100644
--- a/platform/macos/godot_menu_item.h
+++ b/platform/macos/godot_menu_item.h
@@ -52,6 +52,7 @@ enum GlobalMenuCheckType {
Callable hover_callback;
Variant meta;
GlobalMenuCheckType checkable_type;
+ bool checked;
int max_states;
int state;
Ref<Image> img;
diff --git a/platform/macos/godot_menu_item.mm b/platform/macos/godot_menu_item.mm
index 30dac9be9b..479542113a 100644
--- a/platform/macos/godot_menu_item.mm
+++ b/platform/macos/godot_menu_item.mm
@@ -31,4 +31,18 @@
#include "godot_menu_item.h"
@implementation GodotMenuItem
+
+- (id)init {
+ self = [super init];
+
+ self->callback = Callable();
+ self->key_callback = Callable();
+ self->checkable_type = GlobalMenuCheckType::CHECKABLE_TYPE_NONE;
+ self->checked = false;
+ self->max_states = 0;
+ self->state = 0;
+
+ return self;
+}
+
@end
diff --git a/platform/macos/native_menu_macos.mm b/platform/macos/native_menu_macos.mm
index 1ae1137ca0..802d58dc26 100644
--- a/platform/macos/native_menu_macos.mm
+++ b/platform/macos/native_menu_macos.mm
@@ -373,12 +373,7 @@ int NativeMenuMacOS::add_submenu_item(const RID &p_rid, const String &p_label, c
menu_item = [md->menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index];
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
- obj->callback = Callable();
- obj->key_callback = Callable();
obj->meta = p_tag;
- obj->checkable_type = CHECKABLE_TYPE_NONE;
- obj->max_states = 0;
- obj->state = 0;
[menu_item setRepresentedObject:obj];
[md_sub->menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]];
@@ -417,9 +412,6 @@ int NativeMenuMacOS::add_item(const RID &p_rid, const String &p_label, const Cal
obj->callback = p_callback;
obj->key_callback = p_key_callback;
obj->meta = p_tag;
- obj->checkable_type = CHECKABLE_TYPE_NONE;
- obj->max_states = 0;
- obj->state = 0;
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
@@ -438,8 +430,6 @@ int NativeMenuMacOS::add_check_item(const RID &p_rid, const String &p_label, con
obj->key_callback = p_key_callback;
obj->meta = p_tag;
obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
- obj->max_states = 0;
- obj->state = 0;
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
@@ -457,9 +447,6 @@ int NativeMenuMacOS::add_icon_item(const RID &p_rid, const Ref<Texture2D> &p_ico
obj->callback = p_callback;
obj->key_callback = p_key_callback;
obj->meta = p_tag;
- obj->checkable_type = CHECKABLE_TYPE_NONE;
- obj->max_states = 0;
- obj->state = 0;
DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
obj->img = p_icon->get_image();
@@ -489,8 +476,6 @@ int NativeMenuMacOS::add_icon_check_item(const RID &p_rid, const Ref<Texture2D>
obj->key_callback = p_key_callback;
obj->meta = p_tag;
obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX;
- obj->max_states = 0;
- obj->state = 0;
DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
obj->img = p_icon->get_image();
@@ -520,8 +505,6 @@ int NativeMenuMacOS::add_radio_check_item(const RID &p_rid, const String &p_labe
obj->key_callback = p_key_callback;
obj->meta = p_tag;
obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
- obj->max_states = 0;
- obj->state = 0;
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
@@ -540,8 +523,6 @@ int NativeMenuMacOS::add_icon_radio_check_item(const RID &p_rid, const Ref<Textu
obj->key_callback = p_key_callback;
obj->meta = p_tag;
obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON;
- obj->max_states = 0;
- obj->state = 0;
DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
if (ds && p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0 && p_icon->get_image().is_valid()) {
obj->img = p_icon->get_image();
@@ -570,7 +551,6 @@ int NativeMenuMacOS::add_multistate_item(const RID &p_rid, const String &p_label
obj->callback = p_callback;
obj->key_callback = p_key_callback;
obj->meta = p_tag;
- obj->checkable_type = CHECKABLE_TYPE_NONE;
obj->max_states = p_max_states;
obj->state = p_default_state;
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
@@ -640,7 +620,10 @@ bool NativeMenuMacOS::is_item_checked(const RID &p_rid, int p_idx) const {
ERR_FAIL_COND_V(p_idx >= item_start + item_count, false);
const NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
if (menu_item) {
- return ([menu_item state] == NSControlStateValueOn);
+ const GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ return obj->checked;
+ }
}
return false;
}
@@ -958,10 +941,14 @@ void NativeMenuMacOS::set_item_checked(const RID &p_rid, int p_idx, bool p_check
ERR_FAIL_COND(p_idx >= item_start + item_count);
NSMenuItem *menu_item = [md->menu itemAtIndex:p_idx];
if (menu_item) {
- if (p_checked) {
- [menu_item setState:NSControlStateValueOn];
- } else {
- [menu_item setState:NSControlStateValueOff];
+ GodotMenuItem *obj = [menu_item representedObject];
+ if (obj) {
+ obj->checked = p_checked;
+ if (p_checked) {
+ [menu_item setState:NSControlStateValueOn];
+ } else {
+ [menu_item setState:NSControlStateValueOff];
+ }
}
}
}
diff --git a/platform/web/audio_driver_web.cpp b/platform/web/audio_driver_web.cpp
index dd986e650c..b24c6cb1fd 100644
--- a/platform/web/audio_driver_web.cpp
+++ b/platform/web/audio_driver_web.cpp
@@ -33,6 +33,8 @@
#include "godot_audio.h"
#include "core/config/project_settings.h"
+#include "core/object/object.h"
+#include "scene/main/node.h"
#include "servers/audio/audio_stream.h"
#include <emscripten.h>
@@ -51,6 +53,33 @@ void AudioDriverWeb::_latency_update_callback(float p_latency) {
AudioDriverWeb::audio_context.output_latency = p_latency;
}
+void AudioDriverWeb::_sample_playback_finished_callback(const char *p_playback_object_id) {
+ const ObjectID playback_id = ObjectID(String::to_int(p_playback_object_id));
+
+ Object *playback_object = ObjectDB::get_instance(playback_id);
+ if (playback_object == nullptr) {
+ return;
+ }
+ Ref<AudioSamplePlayback> playback = Object::cast_to<AudioSamplePlayback>(playback_object);
+ if (playback.is_null()) {
+ return;
+ }
+
+ Object *player_object = ObjectDB::get_instance(playback->player_id);
+ if (player_object == nullptr) {
+ return;
+ }
+ Node *player = Object::cast_to<Node>(player_object);
+ if (player == nullptr) {
+ return;
+ }
+
+ const StringName finished = SNAME("finished");
+ if (player->has_signal(finished)) {
+ player->emit_signal(finished);
+ }
+}
+
void AudioDriverWeb::_audio_driver_process(int p_from, int p_samples) {
int32_t *stream_buffer = reinterpret_cast<int32_t *>(output_rb);
const int max_samples = memarr_len(output_rb);
@@ -132,6 +161,9 @@ Error AudioDriverWeb::init() {
if (!input_rb) {
return ERR_OUT_OF_MEMORY;
}
+
+ godot_audio_sample_set_finished_callback(&_sample_playback_finished_callback);
+
return OK;
}
diff --git a/platform/web/audio_driver_web.h b/platform/web/audio_driver_web.h
index 298ad90fae..46c5ce4de1 100644
--- a/platform/web/audio_driver_web.h
+++ b/platform/web/audio_driver_web.h
@@ -58,6 +58,7 @@ private:
WASM_EXPORT static void _state_change_callback(int p_state);
WASM_EXPORT static void _latency_update_callback(float p_latency);
+ WASM_EXPORT static void _sample_playback_finished_callback(const char *p_playback_object_id);
static AudioDriverWeb *singleton;
diff --git a/platform/web/detect.py b/platform/web/detect.py
index cb4dac1125..79485ea28a 100644
--- a/platform/web/detect.py
+++ b/platform/web/detect.py
@@ -78,6 +78,7 @@ def get_flags():
# -Os reduces file size by around 5 MiB over -O3. -Oz only saves about
# 100 KiB over -Os, which does not justify the negative impact on
# run-time performance.
+ # Note that this overrides the "auto" behavior for target/dev_build.
"optimize": "size",
}
diff --git a/platform/web/godot_audio.h b/platform/web/godot_audio.h
index 8bebbcf7de..dd5bec00cf 100644
--- a/platform/web/godot_audio.h
+++ b/platform/web/godot_audio.h
@@ -57,6 +57,7 @@ extern void godot_audio_sample_set_pause(const char *p_playback_object_id, bool
extern int godot_audio_sample_is_active(const char *p_playback_object_id);
extern void godot_audio_sample_update_pitch_scale(const char *p_playback_object_id, float p_pitch_scale);
extern void godot_audio_sample_set_volumes_linear(const char *p_playback_object_id, int *p_buses_buf, int p_buses_size, float *p_volumes_buf, int p_volumes_size);
+extern void godot_audio_sample_set_finished_callback(void (*p_callback)(const char *));
extern void godot_audio_sample_bus_set_count(int p_count);
extern void godot_audio_sample_bus_remove(int p_index);
diff --git a/platform/web/js/libs/library_godot_audio.js b/platform/web/js/libs/library_godot_audio.js
index 531dbdaeab..0b16b07261 100644
--- a/platform/web/js/libs/library_godot_audio.js
+++ b/platform/web/js/libs/library_godot_audio.js
@@ -687,9 +687,15 @@ class SampleNode {
}
switch (self.getSample().loopMode) {
- case 'disabled':
+ case 'disabled': {
+ const id = this.id;
self.stop();
- break;
+ if (GodotAudio.sampleFinishedCallback != null) {
+ const idCharPtr = GodotRuntime.allocString(id);
+ GodotAudio.sampleFinishedCallback(idCharPtr);
+ GodotRuntime.free(idCharPtr);
+ }
+ } break;
case 'forward':
case 'backward':
self.restart();
@@ -1090,6 +1096,12 @@ const _GodotAudio = {
busSolo: null,
Bus,
+ /**
+ * Callback to signal that a sample has finished.
+ * @type {(playbackObjectIdPtr: number) => void | null}
+ */
+ sampleFinishedCallback: null,
+
/** @type {AudioContext} */
ctx: null,
input: null,
@@ -1764,6 +1776,17 @@ const _GodotAudio = {
godot_audio_sample_bus_set_mute: function (bus, enable) {
GodotAudio.set_sample_bus_mute(bus, Boolean(enable));
},
+
+ godot_audio_sample_set_finished_callback__proxy: 'sync',
+ godot_audio_sample_set_finished_callback__sig: 'vi',
+ /**
+ * Sets the finished callback
+ * @param {Number} callbackPtr Finished callback pointer
+ * @returns {void}
+ */
+ godot_audio_sample_set_finished_callback: function (callbackPtr) {
+ GodotAudio.sampleFinishedCallback = GodotRuntime.get_func(callbackPtr);
+ },
};
autoAddDeps(_GodotAudio, '$GodotAudio');
diff --git a/platform/web/js/libs/library_godot_input.js b/platform/web/js/libs/library_godot_input.js
index 7ea89d553f..6e3b97023d 100644
--- a/platform/web/js/libs/library_godot_input.js
+++ b/platform/web/js/libs/library_godot_input.js
@@ -112,6 +112,7 @@ const GodotIME = {
ime.style.top = '0px';
ime.style.width = '100%';
ime.style.height = '40px';
+ ime.style.pointerEvents = 'none';
ime.style.display = 'none';
ime.contentEditable = 'true';
diff --git a/platform/web/web_main.cpp b/platform/web/web_main.cpp
index 04513f6d57..d0c3bd7c0e 100644
--- a/platform/web/web_main.cpp
+++ b/platform/web/web_main.cpp
@@ -35,6 +35,8 @@
#include "core/config/engine.h"
#include "core/io/resource_loader.h"
#include "main/main.h"
+#include "scene/main/scene_tree.h"
+#include "scene/main/window.h" // SceneTree only forward declares it.
#include <emscripten/emscripten.h>
#include <stdlib.h>
@@ -130,7 +132,7 @@ extern EMSCRIPTEN_KEEPALIVE int godot_web_main(int argc, char *argv[]) {
if (Engine::get_singleton()->is_project_manager_hint() && FileAccess::exists("/tmp/preload.zip")) {
PackedStringArray ps;
ps.push_back("/tmp/preload.zip");
- os->get_main_loop()->emit_signal(SNAME("files_dropped"), ps, -1);
+ SceneTree::get_singleton()->get_root()->emit_signal(SNAME("files_dropped"), ps);
}
#endif
emscripten_set_main_loop(main_loop_callback, -1, false);
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 8d26a705a9..750e8bb54c 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -38,6 +38,7 @@
#include "core/version.h"
#include "drivers/png/png_driver_common.h"
#include "main/main.h"
+#include "scene/resources/texture.h"
#if defined(VULKAN_ENABLED)
#include "rendering_context_driver_vulkan_windows.h"
@@ -2008,7 +2009,7 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window)
}
if (p_mode == WINDOW_MODE_WINDOWED) {
- ShowWindow(wd.hWnd, SW_RESTORE);
+ ShowWindow(wd.hWnd, SW_NORMAL);
wd.maximized = false;
wd.minimized = false;
}
@@ -3807,9 +3808,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
case WM_ACTIVATE: {
// Activation can happen just after the window has been created, even before the callbacks are set.
// Therefore, it's safer to defer the delivery of the event.
- if (!windows[window_id].activate_timer_id) {
- windows[window_id].activate_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr);
- }
+ // It's important to set an nIDEvent different from the SetTimer for move_timer_id because
+ // if the same nIDEvent is passed, the timer is replaced and the same timer_id is returned.
+ windows[window_id].activate_timer_id = SetTimer(windows[window_id].hWnd, DisplayServerWindows::TIMER_ID_WINDOW_ACTIVATION, USER_TIMER_MINIMUM, (TIMERPROC) nullptr);
windows[window_id].activate_state = GET_WM_ACTIVATE_STATE(wParam, lParam);
return 0;
} break;
@@ -4727,7 +4728,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
case WM_ENTERSIZEMOVE: {
Input::get_singleton()->release_pressed_events();
- windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr);
+ windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, DisplayServerWindows::TIMER_ID_MOVE_REDRAW, USER_TIMER_MINIMUM, (TIMERPROC) nullptr);
} break;
case WM_EXITSIZEMOVE: {
KillTimer(windows[window_id].hWnd, windows[window_id].move_timer_id);
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 382f18c239..c2f4de7d81 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -332,6 +332,11 @@ class DisplayServerWindows : public DisplayServer {
String tablet_driver;
Vector<String> tablet_drivers;
+ enum TimerID {
+ TIMER_ID_MOVE_REDRAW = 1,
+ TIMER_ID_WINDOW_ACTIVATION = 2,
+ };
+
enum {
KEY_EVENT_BUFFER_SIZE = 512
};
diff --git a/platform/windows/native_menu_windows.cpp b/platform/windows/native_menu_windows.cpp
index d9dc28e9d9..fde55918e4 100644
--- a/platform/windows/native_menu_windows.cpp
+++ b/platform/windows/native_menu_windows.cpp
@@ -81,22 +81,6 @@ void NativeMenuWindows::_menu_activate(HMENU p_menu, int p_index) const {
if (GetMenuItemInfoW(md->menu, p_index, true, &item)) {
MenuItemData *item_data = (MenuItemData *)item.dwItemData;
if (item_data) {
- if (item_data->max_states > 0) {
- item_data->state++;
- if (item_data->state >= item_data->max_states) {
- item_data->state = 0;
- }
- }
-
- if (item_data->checkable_type == CHECKABLE_TYPE_CHECK_BOX) {
- if ((item.fState & MFS_CHECKED) == MFS_CHECKED) {
- item.fState &= ~MFS_CHECKED;
- } else {
- item.fState |= MFS_CHECKED;
- }
- SetMenuItemInfoW(md->menu, p_index, true, &item);
- }
-
if (item_data->callback.is_valid()) {
Variant ret;
Callable::CallError ce;
@@ -619,9 +603,12 @@ bool NativeMenuWindows::is_item_checked(const RID &p_rid, int p_idx) const {
MENUITEMINFOW item;
ZeroMemory(&item, sizeof(item));
item.cbSize = sizeof(item);
- item.fMask = MIIM_STATE;
+ item.fMask = MIIM_STATE | MIIM_DATA;
if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
- return (item.fState & MFS_CHECKED) == MFS_CHECKED;
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ return item_data->checked;
+ }
}
return false;
}
@@ -861,12 +848,16 @@ void NativeMenuWindows::set_item_checked(const RID &p_rid, int p_idx, bool p_che
MENUITEMINFOW item;
ZeroMemory(&item, sizeof(item));
item.cbSize = sizeof(item);
- item.fMask = MIIM_STATE;
+ item.fMask = MIIM_STATE | MIIM_DATA;
if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) {
- if (p_checked) {
- item.fState |= MFS_CHECKED;
- } else {
- item.fState &= ~MFS_CHECKED;
+ MenuItemData *item_data = (MenuItemData *)item.dwItemData;
+ if (item_data) {
+ item_data->checked = p_checked;
+ if (p_checked) {
+ item.fState |= MFS_CHECKED;
+ } else {
+ item.fState &= ~MFS_CHECKED;
+ }
}
SetMenuItemInfoW(md->menu, p_idx, true, &item);
}
diff --git a/platform/windows/native_menu_windows.h b/platform/windows/native_menu_windows.h
index 5c4aaa52c8..235a4b332a 100644
--- a/platform/windows/native_menu_windows.h
+++ b/platform/windows/native_menu_windows.h
@@ -51,6 +51,7 @@ class NativeMenuWindows : public NativeMenu {
Callable callback;
Variant meta;
GlobalMenuCheckType checkable_type;
+ bool checked = false;
int max_states = 0;
int state = 0;
Ref<Image> img;
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp
index b3f735e044..014419573c 100644
--- a/scene/2d/animated_sprite_2d.cpp
+++ b/scene/2d/animated_sprite_2d.cpp
@@ -574,7 +574,7 @@ StringName AnimatedSprite2D::get_animation() const {
PackedStringArray AnimatedSprite2D::get_configuration_warnings() const {
PackedStringArray warnings = Node2D::get_configuration_warnings();
if (frames.is_null()) {
- warnings.push_back(RTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite2D to display frames."));
+ warnings.push_back(RTR("A SpriteFrames resource must be created or set in the \"Sprite Frames\" property in order for AnimatedSprite2D to display frames."));
}
return warnings;
}
diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp
index 514c5e7a8f..7020d162fe 100644
--- a/scene/2d/camera_2d.cpp
+++ b/scene/2d/camera_2d.cpp
@@ -302,6 +302,12 @@ void Camera2D::_notification(int p_what) {
_interpolation_data.xform_prev = _interpolation_data.xform_curr;
} break;
+ case NOTIFICATION_PAUSED: {
+ if (is_physics_interpolated_and_enabled()) {
+ _update_scroll();
+ }
+ } break;
+
case NOTIFICATION_TRANSFORM_CHANGED: {
if ((!position_smoothing_enabled && !is_physics_interpolated_and_enabled()) || _is_editing_in_editor()) {
_update_scroll();
diff --git a/scene/2d/camera_2d.h b/scene/2d/camera_2d.h
index 8754e35e88..be2da8b97a 100644
--- a/scene/2d/camera_2d.h
+++ b/scene/2d/camera_2d.h
@@ -108,7 +108,7 @@ protected:
struct InterpolationData {
Transform2D xform_curr;
Transform2D xform_prev;
- uint32_t last_update_physics_tick = 0;
+ uint32_t last_update_physics_tick = UINT32_MAX; // Ensure tick 0 is detected as a change.
} _interpolation_data;
void _ensure_update_interpolation_data();
diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp
index 9e3e6ea583..d0fae611d8 100644
--- a/scene/2d/navigation_agent_2d.cpp
+++ b/scene/2d/navigation_agent_2d.cpp
@@ -671,8 +671,6 @@ void NavigationAgent2D::_update_navigation() {
return;
}
- update_frame_id = Engine::get_singleton()->get_physics_frames();
-
Vector2 origin = agent_parent->get_global_position();
bool reload_path = false;
@@ -767,7 +765,6 @@ void NavigationAgent2D::_request_repath() {
target_reached = false;
navigation_finished = false;
last_waypoint_reached = false;
- update_frame_id = 0;
}
bool NavigationAgent2D::_is_last_waypoint() const {
diff --git a/scene/2d/navigation_agent_2d.h b/scene/2d/navigation_agent_2d.h
index 0acfc82162..8741f578d0 100644
--- a/scene/2d/navigation_agent_2d.h
+++ b/scene/2d/navigation_agent_2d.h
@@ -91,8 +91,6 @@ class NavigationAgent2D : public Node {
bool target_reached = false;
bool navigation_finished = true;
bool last_waypoint_reached = false;
- // No initialized on purpose
- uint32_t update_frame_id = 0;
// Debug properties for exposed bindings
bool debug_enabled = false;
diff --git a/scene/2d/parallax_2d.cpp b/scene/2d/parallax_2d.cpp
index aacab3213d..9dd9d4a376 100644
--- a/scene/2d/parallax_2d.cpp
+++ b/scene/2d/parallax_2d.cpp
@@ -31,6 +31,7 @@
#include "parallax_2d.h"
#include "core/config/project_settings.h"
+#include "scene/main/viewport.h"
void Parallax2D::_notification(int p_what) {
switch (p_what) {
@@ -72,7 +73,11 @@ void Parallax2D::_validate_property(PropertyInfo &p_property) const {
void Parallax2D::_camera_moved(const Transform2D &p_transform, const Point2 &p_screen_offset, const Point2 &p_adj_screen_pos) {
if (!ignore_camera_scroll) {
- set_screen_offset(p_adj_screen_pos);
+ if (get_viewport() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
+ set_screen_offset((p_adj_screen_pos + Vector2(0.5, 0.5)).floor());
+ } else {
+ set_screen_offset(p_adj_screen_pos);
+ }
}
}
@@ -86,11 +91,10 @@ void Parallax2D::_update_scroll() {
}
Point2 scroll_ofs = screen_offset;
- Size2 vps = get_viewport_rect().size;
- if (Engine::get_singleton()->is_editor_hint()) {
- vps = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));
- } else {
+ if (!Engine::get_singleton()->is_editor_hint()) {
+ Size2 vps = get_viewport_rect().size;
+
if (limit_begin.x <= limit_end.x - vps.x) {
scroll_ofs.x = CLAMP(scroll_ofs.x, limit_begin.x, limit_end.x - vps.x);
}
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index d1f1c97ca2..48ade1e5cc 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -996,6 +996,7 @@ TileMap::TileMap() {
base_property_helper.register_property(PropertyInfo(Variant::INT, "z_index"), defaults->get_z_index(), &TileMap::set_layer_z_index, &TileMap::get_layer_z_index);
base_property_helper.register_property(PropertyInfo(Variant::BOOL, "navigation_enabled"), defaults->is_navigation_enabled(), &TileMap::set_layer_navigation_enabled, &TileMap::is_layer_navigation_enabled);
base_property_helper.register_property(PropertyInfo(Variant::PACKED_INT32_ARRAY, "tile_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), Vector<int>(), &TileMap::_set_layer_tile_data, &TileMap::_get_tile_map_data_using_compatibility_format);
+ PropertyListHelper::register_base_helper(&base_property_helper);
memdelete(defaults);
}
diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp
index dff413f5d2..5bbb724e2f 100644
--- a/scene/3d/navigation_agent_3d.cpp
+++ b/scene/3d/navigation_agent_3d.cpp
@@ -737,8 +737,6 @@ void NavigationAgent3D::_update_navigation() {
return;
}
- update_frame_id = Engine::get_singleton()->get_physics_frames();
-
Vector3 origin = agent_parent->get_global_position();
bool reload_path = false;
@@ -835,7 +833,6 @@ void NavigationAgent3D::_request_repath() {
target_reached = false;
navigation_finished = false;
last_waypoint_reached = false;
- update_frame_id = 0;
}
bool NavigationAgent3D::_is_last_waypoint() const {
diff --git a/scene/3d/navigation_agent_3d.h b/scene/3d/navigation_agent_3d.h
index ade6afd445..d5721a56c8 100644
--- a/scene/3d/navigation_agent_3d.h
+++ b/scene/3d/navigation_agent_3d.h
@@ -98,8 +98,6 @@ class NavigationAgent3D : public Node {
bool target_reached = false;
bool navigation_finished = true;
bool last_waypoint_reached = false;
- // No initialized on purpose
- uint32_t update_frame_id = 0;
// Debug properties for exposed bindings
bool debug_enabled = false;
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index d08aeb1de2..50218a6d86 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -984,7 +984,7 @@ void Sprite3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "hframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_hframes", "get_hframes");
ADD_PROPERTY(PropertyInfo(Variant::INT, "vframes", PROPERTY_HINT_RANGE, "1,16384,1"), "set_vframes", "get_vframes");
ADD_PROPERTY(PropertyInfo(Variant::INT, "frame"), "set_frame", "get_frame");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frame_coords", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "frame_coords", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_frame_coords", "get_frame_coords");
ADD_GROUP("Region", "region_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "region_enabled"), "set_region_enabled", "is_region_enabled");
ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect", PROPERTY_HINT_NONE, "suffix:px"), "set_region_rect", "get_region_rect");
@@ -1435,7 +1435,7 @@ StringName AnimatedSprite3D::get_animation() const {
PackedStringArray AnimatedSprite3D::get_configuration_warnings() const {
PackedStringArray warnings = SpriteBase3D::get_configuration_warnings();
if (frames.is_null()) {
- warnings.push_back(RTR("A SpriteFrames resource must be created or set in the \"Frames\" property in order for AnimatedSprite3D to display frames."));
+ warnings.push_back(RTR("A SpriteFrames resource must be created or set in the \"Sprite Frames\" property in order for AnimatedSprite3D to display frames."));
}
return warnings;
}
diff --git a/scene/audio/audio_stream_player_internal.cpp b/scene/audio/audio_stream_player_internal.cpp
index b3a55ddee0..6653e01f25 100644
--- a/scene/audio/audio_stream_player_internal.cpp
+++ b/scene/audio/audio_stream_player_internal.cpp
@@ -152,6 +152,7 @@ Ref<AudioStreamPlayback> AudioStreamPlayerInternal::play_basic() {
Ref<AudioSamplePlayback> sample_playback;
sample_playback.instantiate();
sample_playback->stream = stream;
+ sample_playback->player_id = node->get_instance_id();
stream_playback->set_sample_playback(sample_playback);
}
} else if (!stream->is_meta_stream()) {
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 0682c11a9b..1d53edbfa6 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -1732,11 +1732,15 @@ void Control::_size_changed() {
new_size_cache.height = minimum_size.height;
}
- bool pos_changed = new_pos_cache != data.pos_cache;
- bool size_changed = new_size_cache != data.size_cache;
+ bool pos_changed = !new_pos_cache.is_equal_approx(data.pos_cache);
+ bool size_changed = !new_size_cache.is_equal_approx(data.size_cache);
- data.pos_cache = new_pos_cache;
- data.size_cache = new_size_cache;
+ if (pos_changed) {
+ data.pos_cache = new_pos_cache;
+ }
+ if (size_changed) {
+ data.size_cache = new_size_cache;
+ }
if (is_inside_tree()) {
if (pos_changed || size_changed) {
@@ -1751,12 +1755,10 @@ void Control::_size_changed() {
}
if (pos_changed && !size_changed) {
- _update_canvas_item_transform(); //move because it won't be updated
- }
- } else {
- if (pos_changed) {
- _notify_transform();
+ _update_canvas_item_transform();
}
+ } else if (pos_changed) {
+ _notify_transform();
}
}
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index c9372525bd..8047369ab1 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -1350,6 +1350,7 @@ void FileDialog::_bind_methods() {
base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults.name, &FileDialog::set_option_name, &FileDialog::get_option_name);
base_property_helper.register_property(PropertyInfo(Variant::PACKED_STRING_ARRAY, "values"), defaults.values, &FileDialog::set_option_values, &FileDialog::get_option_values);
base_property_helper.register_property(PropertyInfo(Variant::INT, "default"), defaults.default_idx, &FileDialog::set_option_default, &FileDialog::get_option_default);
+ PropertyListHelper::register_base_helper(&base_property_helper);
}
void FileDialog::set_show_hidden_files(bool p_show) {
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 33756dc1fd..55a2c607e3 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -546,6 +546,11 @@ void GraphEdit::_graph_node_slot_updated(int p_index, Node *p_node) {
GraphNode *graph_node = Object::cast_to<GraphNode>(p_node);
ERR_FAIL_NULL(graph_node);
+ // Update all adjacent connections during the next redraw.
+ for (const Ref<Connection> &conn : connection_map[graph_node->get_name()]) {
+ conn->_cache.dirty = true;
+ }
+
minimap->queue_redraw();
queue_redraw();
connections_layer->queue_redraw();
@@ -782,7 +787,9 @@ Rect2 GraphEdit::_compute_shrinked_frame_rect(const GraphFrame *p_frame) {
return Rect2(p_frame->get_position_offset(), Size2());
}
- min_point -= Size2(autoshrink_margin, autoshrink_margin);
+ const Size2 titlebar_size = p_frame->get_titlebar_size();
+
+ min_point -= Size2(autoshrink_margin, MAX(autoshrink_margin, titlebar_size.y));
max_point += Size2(autoshrink_margin, autoshrink_margin);
return Rect2(min_point, max_point - min_point);
diff --git a/scene/gui/graph_frame.cpp b/scene/gui/graph_frame.cpp
index 8cd7dbbeb5..e85d007262 100644
--- a/scene/gui/graph_frame.cpp
+++ b/scene/gui/graph_frame.cpp
@@ -262,6 +262,10 @@ HBoxContainer *GraphFrame::get_titlebar_hbox() {
return titlebar_hbox;
}
+Size2 GraphFrame::get_titlebar_size() const {
+ return titlebar_hbox->get_size() + theme_cache.titlebar->get_minimum_size();
+}
+
void GraphFrame::set_drag_margin(int p_margin) {
drag_margin = p_margin;
}
diff --git a/scene/gui/graph_frame.h b/scene/gui/graph_frame.h
index 21346586c8..2af09cf872 100644
--- a/scene/gui/graph_frame.h
+++ b/scene/gui/graph_frame.h
@@ -89,6 +89,7 @@ public:
int get_autoshrink_margin() const;
HBoxContainer *get_titlebar_hbox();
+ Size2 get_titlebar_size() const;
void set_drag_margin(int p_margin);
int get_drag_margin() const;
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index 933b4df6e3..bf16c0699e 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -1905,6 +1905,7 @@ void ItemList::_bind_methods() {
base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &ItemList::set_item_icon, &ItemList::get_item_icon);
base_property_helper.register_property(PropertyInfo(Variant::BOOL, "selectable"), defaults.selectable, &ItemList::set_item_selectable, &ItemList::is_item_selectable);
base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &ItemList::set_item_disabled, &ItemList::is_item_disabled);
+ PropertyListHelper::register_base_helper(&base_property_helper);
}
ItemList::ItemList() {
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index 998f99b2f9..e99187d283 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -198,6 +198,7 @@ void MenuButton::_bind_methods() {
base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id);
base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled);
base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator);
+ PropertyListHelper::register_base_helper(&base_property_helper);
}
void MenuButton::set_disable_shortcuts(bool p_disabled) {
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index da15b44bdc..a1425fb847 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -577,6 +577,7 @@ void OptionButton::_bind_methods() {
base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id, &OptionButton::_dummy_setter, &OptionButton::get_item_id);
base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &OptionButton::_dummy_setter, &OptionButton::is_item_disabled);
base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator, &OptionButton::_dummy_setter, &OptionButton::is_item_separator);
+ PropertyListHelper::register_base_helper(&base_property_helper);
}
void OptionButton::set_disable_shortcuts(bool p_disabled) {
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 4f07fdb87b..f62421061b 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -2314,6 +2314,16 @@ bool PopupMenu::is_prefer_native_menu() const {
return prefer_native;
}
+bool PopupMenu::is_native_menu() const {
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ return false;
+ }
+#endif
+
+ return global_menu.is_valid();
+}
+
bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only) {
ERR_FAIL_COND_V(p_event.is_null(), false);
Key code = Key::NONE;
@@ -2643,6 +2653,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_prefer_native_menu", "enabled"), &PopupMenu::set_prefer_native_menu);
ClassDB::bind_method(D_METHOD("is_prefer_native_menu"), &PopupMenu::is_prefer_native_menu);
+ ClassDB::bind_method(D_METHOD("is_native_menu"), &PopupMenu::is_native_menu);
ClassDB::bind_method(D_METHOD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0));
ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0));
@@ -2813,6 +2824,7 @@ void PopupMenu::_bind_methods() {
base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id, &PopupMenu::set_item_id, &PopupMenu::get_item_id);
base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &PopupMenu::set_item_disabled, &PopupMenu::is_item_disabled);
base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator, &PopupMenu::set_item_as_separator, &PopupMenu::is_item_separator);
+ PropertyListHelper::register_base_helper(&base_property_helper);
}
void PopupMenu::popup(const Rect2i &p_bounds) {
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index c6eef03aca..5313dae404 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -330,6 +330,8 @@ public:
void set_prefer_native_menu(bool p_enabled);
bool is_prefer_native_menu() const;
+ bool is_native_menu() const;
+
void scroll_to_item(int p_idx);
bool activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only = false);
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 8ffa0f8c63..5ef02bf19d 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -2098,7 +2098,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
handled = true;
}
if (k->is_action("ui_down", true) && vscroll->is_visible_in_tree()) {
- vscroll->scroll(vscroll->get_value() + theme_cache.normal_font->get_height(theme_cache.normal_font_size));
+ vscroll->scroll(theme_cache.normal_font->get_height(theme_cache.normal_font_size));
handled = true;
}
if (k->is_action("ui_home", true) && vscroll->is_visible_in_tree()) {
@@ -4947,10 +4947,10 @@ void RichTextLabel::append_text(const String &p_bbcode) {
tag_stack.push_front("outline_size");
} else if (bbcode_name == "fade") {
- int start_index = 0;
+ int start_index = brk_pos;
OptionMap::Iterator start_option = bbcode_options.find("start");
if (start_option) {
- start_index = start_option->value.to_int();
+ start_index += start_option->value.to_int();
}
int length = 10;
diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp
index ddc757c452..1ae18f5728 100644
--- a/scene/gui/tab_bar.cpp
+++ b/scene/gui/tab_bar.cpp
@@ -1873,6 +1873,7 @@ void TabBar::_bind_methods() {
base_property_helper.register_property(PropertyInfo(Variant::STRING, "tooltip"), defaults.tooltip, &TabBar::set_tab_tooltip, &TabBar::get_tab_tooltip);
base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &TabBar::set_tab_icon, &TabBar::get_tab_icon);
base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &TabBar::set_tab_disabled, &TabBar::is_tab_disabled);
+ PropertyListHelper::register_base_helper(&base_property_helper);
}
TabBar::TabBar() {
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 7b682daa83..9cc59f1def 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -1643,21 +1643,14 @@ void TextEdit::_notification(int p_what) {
} break;
case NOTIFICATION_DRAG_END: {
- if (is_drag_successful()) {
- if (selection_drag_attempt) {
- // Dropped elsewhere.
- if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
- delete_selection();
- } else if (deselect_on_focus_loss_enabled) {
- deselect();
- }
- }
- }
- if (drag_caret_index >= 0) {
- if (drag_caret_index < carets.size()) {
- remove_caret(drag_caret_index);
+ remove_drag_caret();
+ if (selection_drag_attempt && is_drag_successful()) {
+ // Dropped elsewhere.
+ if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
+ delete_selection();
+ } else if (deselect_on_focus_loss_enabled) {
+ deselect();
}
- drag_caret_index = -1;
}
selection_drag_attempt = false;
drag_action = false;
@@ -4606,6 +4599,15 @@ void TextEdit::remove_caret(int p_caret) {
}
}
+void TextEdit::remove_drag_caret() {
+ if (drag_caret_index >= 0) {
+ if (drag_caret_index < carets.size()) {
+ remove_caret(drag_caret_index);
+ }
+ drag_caret_index = -1;
+ }
+}
+
void TextEdit::remove_secondary_carets() {
if (carets.size() == 1) {
return;
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 6ed5cf4bdc..4d9d169c1c 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -881,6 +881,7 @@ public:
int add_caret(int p_line, int p_column);
void remove_caret(int p_caret);
+ void remove_drag_caret();
void remove_secondary_carets();
int get_caret_count() const;
void add_caret_at_carets(bool p_below);
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 5bbf8ebff4..0396f3ab4a 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -177,6 +177,12 @@ void Node::_notification(int p_notification) {
}
} break;
+ case NOTIFICATION_PAUSED: {
+ if (is_physics_interpolated_and_enabled() && is_inside_tree()) {
+ reset_physics_interpolation();
+ }
+ } break;
+
case NOTIFICATION_PATH_RENAMED: {
if (data.path_cache) {
memdelete(data.path_cache);
diff --git a/scene/property_list_helper.cpp b/scene/property_list_helper.cpp
index ce258ee8c3..f840aaa759 100644
--- a/scene/property_list_helper.cpp
+++ b/scene/property_list_helper.cpp
@@ -30,6 +30,19 @@
#include "property_list_helper.h"
+Vector<PropertyListHelper *> PropertyListHelper::base_helpers; // static
+
+void PropertyListHelper::clear_base_helpers() { // static
+ for (PropertyListHelper *helper : base_helpers) {
+ helper->clear();
+ }
+ base_helpers.clear();
+}
+
+void PropertyListHelper::register_base_helper(PropertyListHelper *p_helper) { // static
+ base_helpers.push_back(p_helper);
+}
+
const PropertyListHelper::Property *PropertyListHelper::_get_property(const String &p_property, int *r_index) const {
const Vector<String> components = p_property.rsplit("/", true, 1);
if (components.size() < 2 || !components[0].begins_with(prefix)) {
@@ -176,9 +189,8 @@ bool PropertyListHelper::property_get_revert(const String &p_property, Variant &
return false;
}
-PropertyListHelper::~PropertyListHelper() {
- // No object = it's the main helper. Do a cleanup.
- if (!object && is_initialized()) {
+void PropertyListHelper::clear() {
+ if (is_initialized()) {
memdelete(array_length_getter);
for (const KeyValue<String, Property> &E : property_list) {
@@ -187,5 +199,6 @@ PropertyListHelper::~PropertyListHelper() {
memdelete(E.value.getter);
}
}
+ property_list.clear();
}
}
diff --git a/scene/property_list_helper.h b/scene/property_list_helper.h
index 6bc65f6e3e..1ab923e76d 100644
--- a/scene/property_list_helper.h
+++ b/scene/property_list_helper.h
@@ -42,6 +42,8 @@ class PropertyListHelper {
MethodBind *getter = nullptr;
};
+ static Vector<PropertyListHelper *> base_helpers;
+
String prefix;
MethodBind *array_length_getter = nullptr;
HashMap<String, Property> property_list;
@@ -53,6 +55,9 @@ class PropertyListHelper {
int _call_array_length_getter() const;
public:
+ static void clear_base_helpers();
+ static void register_base_helper(PropertyListHelper *p_helper);
+
void set_prefix(const String &p_prefix);
template <typename G>
void set_array_length_getter(G p_array_length_getter) {
@@ -83,7 +88,7 @@ public:
bool property_can_revert(const String &p_property) const;
bool property_get_revert(const String &p_property, Variant &r_value) const;
- ~PropertyListHelper();
+ void clear();
};
#endif // PROPERTY_LIST_HELPER_H
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index 254bd38be7..c0ab636adc 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -3189,6 +3189,20 @@ StringName Animation::method_track_get_name(int p_track, int p_key_idx) const {
return pm->methods[p_key_idx].method;
}
+Array Animation::make_default_bezier_key(float p_value) {
+ const double max_width = length / 2.0;
+ Array new_point;
+ new_point.resize(5);
+
+ new_point[0] = p_value;
+ new_point[1] = MAX(-0.25, -max_width);
+ new_point[2] = 0;
+ new_point[3] = MIN(0.25, max_width);
+ new_point[4] = 0;
+
+ return new_point;
+}
+
int Animation::bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle) {
ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
Track *t = tracks[p_track];
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index e9bfc298a5..cb12b12c0e 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -455,6 +455,7 @@ public:
void track_set_interpolation_type(int p_track, InterpolationType p_interp);
InterpolationType track_get_interpolation_type(int p_track) const;
+ Array make_default_bezier_key(float p_value);
int bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle);
void bezier_track_set_key_value(int p_track, int p_index, real_t p_value);
void bezier_track_set_key_in_handle(int p_track, int p_index, const Vector2 &p_handle, real_t p_balanced_value_time_ratio = 1.0);
diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp
index bc8e0b9015..37d9d57722 100644
--- a/scene/resources/font.cpp
+++ b/scene/resources/font.cpp
@@ -3213,7 +3213,6 @@ void SystemFont::_update_base_font() {
}
_invalidate_rids();
- notify_property_list_changed();
}
void SystemFont::reset_state() {
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index b1742bd5a3..900629f5f8 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -2124,6 +2124,56 @@ void PackedScene::recreate_state() {
#endif
}
+#ifdef TOOLS_ENABLED
+HashSet<StringName> PackedScene::get_scene_groups(const String &p_path) {
+ {
+ Ref<PackedScene> packed_scene = ResourceCache::get_ref(p_path);
+ if (packed_scene.is_valid()) {
+ return packed_scene->get_state()->get_all_groups();
+ }
+ }
+
+ if (p_path.get_extension() == "tscn") {
+ Ref<FileAccess> scene_file = FileAccess::open(p_path, FileAccess::READ);
+ ERR_FAIL_COND_V(scene_file.is_null(), HashSet<StringName>());
+
+ HashSet<StringName> ret;
+ while (!scene_file->eof_reached()) {
+ const String line = scene_file->get_line();
+ if (!line.begins_with("[node")) {
+ continue;
+ }
+
+ int i = line.find("groups=[");
+ if (i == -1) {
+ continue;
+ }
+
+ int j = line.find_char(']', i);
+ while (i < j) {
+ i = line.find_char('"', i);
+ if (i == -1) {
+ break;
+ }
+
+ int k = line.find_char('"', i + 1);
+ if (k == -1) {
+ break;
+ }
+
+ ret.insert(line.substr(i + 1, k - i - 1));
+ i = k + 1;
+ }
+ }
+ return ret;
+ } else {
+ Ref<PackedScene> packed_scene = ResourceLoader::load(p_path);
+ ERR_FAIL_COND_V(packed_scene.is_null(), HashSet<StringName>());
+ return packed_scene->get_state()->get_all_groups();
+ }
+}
+#endif
+
Ref<SceneState> PackedScene::get_state() const {
return state;
}
diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h
index c46a4dd5fe..e26b9f7b90 100644
--- a/scene/resources/packed_scene.h
+++ b/scene/resources/packed_scene.h
@@ -270,6 +270,7 @@ public:
state->set_last_modified_time(p_time);
}
+ static HashSet<StringName> get_scene_groups(const String &p_path);
#endif
Ref<SceneState> get_state() const;
diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp
index 0dc6d16050..7ab150c141 100644
--- a/servers/audio/audio_stream.cpp
+++ b/servers/audio/audio_stream.cpp
@@ -741,6 +741,7 @@ void AudioStreamRandomizer::_bind_methods() {
base_property_helper.set_array_length_getter(&AudioStreamRandomizer::get_streams_count);
base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), defaults.stream, &AudioStreamRandomizer::set_stream, &AudioStreamRandomizer::get_stream);
base_property_helper.register_property(PropertyInfo(Variant::FLOAT, "weight", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), defaults.weight, &AudioStreamRandomizer::set_stream_probability_weight, &AudioStreamRandomizer::get_stream_probability_weight);
+ PropertyListHelper::register_base_helper(&base_property_helper);
}
AudioStreamRandomizer::AudioStreamRandomizer() {
diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h
index 0ca4777d5c..9149109381 100644
--- a/servers/audio/audio_stream.h
+++ b/servers/audio/audio_stream.h
@@ -49,6 +49,7 @@ class AudioSamplePlayback : public RefCounted {
public:
Ref<AudioStream> stream;
+ ObjectID player_id;
float offset = 0.0f;
Vector<AudioFrame> volume_vector;
StringName bus;
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 536fc7a04a..f97ed3d215 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -587,7 +587,7 @@ void RenderForwardClustered::_render_list_with_draw_list(RenderListParameters *p
RD::get_singleton()->draw_list_end();
}
-void RenderForwardClustered::_setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier, bool p_pancake_shadows, int p_index) {
+void RenderForwardClustered::_setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, const Color &p_default_bg_color, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier, bool p_pancake_shadows, int p_index) {
RendererRD::LightStorage *light_storage = RendererRD::LightStorage::get_singleton();
Ref<RenderSceneBuffersRD> rd = p_render_data->render_buffers;
@@ -603,7 +603,7 @@ void RenderForwardClustered::_setup_environment(const RenderDataRD *p_render_dat
}
}
- p_render_data->scene_data->update_ubo(scene_state.uniform_buffers[p_index], get_debug_draw_mode(), env, reflection_probe_instance, p_render_data->camera_attributes, p_flip_y, p_pancake_shadows, p_screen_size, p_default_bg_color, _render_buffers_get_luminance_multiplier(), p_opaque_render_buffers, p_apply_alpha_multiplier);
+ p_render_data->scene_data->update_ubo(scene_state.uniform_buffers[p_index], get_debug_draw_mode(), env, reflection_probe_instance, p_render_data->camera_attributes, p_pancake_shadows, p_screen_size, p_default_bg_color, _render_buffers_get_luminance_multiplier(), p_opaque_render_buffers, p_apply_alpha_multiplier);
// now do implementation UBO
@@ -1732,7 +1732,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
_setup_lightmaps(p_render_data, *p_render_data->lightmaps, p_render_data->scene_data->cam_transform);
_setup_voxelgis(*p_render_data->voxel_gi_instances);
- _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false);
+ _setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, false);
// May have changed due to the above (light buffer enlarged, as an example).
_update_render_base_uniform_set();
@@ -1995,7 +1995,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
// Shadow pass can change the base uniform set samplers.
_update_render_base_uniform_set();
- _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, true, using_motion_pass);
+ _setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, true, using_motion_pass);
RID rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_OPAQUE, p_render_data, radiance_texture, samplers, true);
@@ -2209,7 +2209,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_ALPHA, p_render_data, radiance_texture, samplers, true);
- _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false);
+ _setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, false);
{
uint32_t transparent_color_pass_flags = (color_pass_flags | COLOR_PASS_FLAG_TRANSPARENT) & ~(COLOR_PASS_FLAG_SEPARATE_SPECULAR);
@@ -2562,6 +2562,7 @@ void RenderForwardClustered::_render_shadow_append(RID p_framebuffer, const Page
SceneState::ShadowPass shadow_pass;
RenderSceneDataRD scene_data;
+ scene_data.flip_y = !p_flip_y; // Q: Why is this inverted? Do we assume flip in shadow logic?
scene_data.cam_projection = p_projection;
scene_data.cam_transform = p_transform;
scene_data.view_projection[0] = p_projection;
@@ -2581,7 +2582,7 @@ void RenderForwardClustered::_render_shadow_append(RID p_framebuffer, const Page
render_data.instances = &p_instances;
render_data.render_info = p_render_info;
- _setup_environment(&render_data, true, p_viewport_size, !p_flip_y, Color(), false, false, p_use_pancake, shadow_pass_index);
+ _setup_environment(&render_data, true, p_viewport_size, Color(), false, false, p_use_pancake, shadow_pass_index);
if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_DISABLE_LOD) {
scene_data.screen_mesh_lod_threshold = 0.0;
@@ -2654,6 +2655,7 @@ void RenderForwardClustered::_render_particle_collider_heightfield(RID p_fb, con
RD::get_singleton()->draw_command_begin_label("Render Collider Heightfield");
RenderSceneDataRD scene_data;
+ scene_data.flip_y = true;
scene_data.cam_projection = p_cam_projection;
scene_data.cam_transform = p_cam_transform;
scene_data.view_projection[0] = p_cam_projection;
@@ -2673,7 +2675,7 @@ void RenderForwardClustered::_render_particle_collider_heightfield(RID p_fb, con
_update_render_base_uniform_set();
- _setup_environment(&render_data, true, Vector2(1, 1), true, Color(), false, false, false);
+ _setup_environment(&render_data, true, Vector2(1, 1), Color(), false, false, false);
PassMode pass_mode = PASS_MODE_SHADOW;
@@ -2720,7 +2722,7 @@ void RenderForwardClustered::_render_material(const Transform3D &p_cam_transform
_update_render_base_uniform_set();
- _setup_environment(&render_data, true, Vector2(1, 1), false, Color());
+ _setup_environment(&render_data, true, Vector2(1, 1), Color());
PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL;
_fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode);
@@ -2771,7 +2773,7 @@ void RenderForwardClustered::_render_uv2(const PagedArray<RenderGeometryInstance
_update_render_base_uniform_set();
- _setup_environment(&render_data, true, Vector2(1, 1), false, Color());
+ _setup_environment(&render_data, true, Vector2(1, 1), Color());
PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL;
_fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode);
@@ -2887,7 +2889,7 @@ void RenderForwardClustered::_render_sdfgi(Ref<RenderSceneBuffersRD> p_render_bu
RendererRD::MaterialStorage::store_transform(to_bounds.affine_inverse() * scene_data.cam_transform, scene_state.ubo.sdf_to_bounds);
scene_data.emissive_exposure_normalization = p_exposure_normalization;
- _setup_environment(&render_data, true, Vector2(1, 1), false, Color());
+ _setup_environment(&render_data, true, Vector2(1, 1), Color());
RID rp_uniform_set = _setup_sdfgi_render_pass_uniform_set(p_albedo_texture, p_emission_texture, p_emission_aniso_texture, p_geom_facing_texture, RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default());
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
index ae9e5e7c10..0aa4a0667e 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
@@ -361,7 +361,7 @@ class RenderForwardClustered : public RendererSceneRenderRD {
static RenderForwardClustered *singleton;
- void _setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_opaque_render_buffers = false, bool p_apply_alpha_multiplier = false, bool p_pancake_shadows = false, int p_index = 0);
+ void _setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, const Color &p_default_bg_color, bool p_opaque_render_buffers = false, bool p_apply_alpha_multiplier = false, bool p_pancake_shadows = false, int p_index = 0);
void _setup_voxelgis(const PagedArray<RID> &p_voxelgis);
void _setup_lightmaps(const RenderDataRD *p_render_data, const PagedArray<RID> &p_lightmaps, const Transform3D &p_cam_transform);
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
index 194a70dc22..af190207db 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
@@ -804,7 +804,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
RD::get_singleton()->draw_command_begin_label("Render Setup");
_setup_lightmaps(p_render_data, *p_render_data->lightmaps, p_render_data->scene_data->cam_transform);
- _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false);
+ _setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, false);
// May have changed due to the above (light buffer enlarged, as an example).
_update_render_base_uniform_set();
@@ -953,7 +953,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
// Shadow pass can change the base uniform set samplers.
_update_render_base_uniform_set();
- _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, p_render_data->render_buffers.is_valid());
+ _setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, p_render_data->render_buffers.is_valid());
if (merge_transparent_pass && using_subpass_post_process) {
RENDER_TIMESTAMP("Render Opaque + Transparent + Tonemap");
@@ -1018,11 +1018,6 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
RD::get_singleton()->draw_command_end_label(); // Draw Sky
}
- // rendering effects
- if (ce_has_pre_transparent) {
- _process_compositor_effects(RS::COMPOSITOR_EFFECT_CALLBACK_TYPE_PRE_TRANSPARENT, p_render_data);
- }
-
if (merge_transparent_pass) {
if (render_list[RENDER_LIST_ALPHA].element_info.size() > 0) {
// transparent pass
@@ -1058,6 +1053,11 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
RD::get_singleton()->draw_command_end_label(); // Render 3D Pass / Render Reflection Probe Pass
+ // rendering effects
+ if (ce_has_pre_transparent) {
+ _process_compositor_effects(RS::COMPOSITOR_EFFECT_CALLBACK_TYPE_PRE_TRANSPARENT, p_render_data);
+ }
+
if (scene_state.used_screen_texture) {
// Copy screen texture to backbuffer so we can read from it
_render_buffers_copy_screen_texture(p_render_data);
@@ -1075,7 +1075,7 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
rp_uniform_set = _setup_render_pass_uniform_set(RENDER_LIST_ALPHA, p_render_data, radiance_texture, samplers, true);
// this may be needed if we re-introduced steps that change info, not sure which do so in the previous implementation
- //_setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false);
+ //_setup_environment(p_render_data, is_reflection_probe, screen_size, p_default_bg_color, false);
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, rp_uniform_set, spec_constant_base_flags, 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);
render_list_params.framebuffer_format = fb_format;
@@ -1310,6 +1310,7 @@ void RenderForwardMobile::_render_shadow_append(RID p_framebuffer, const PagedAr
}
RenderSceneDataRD scene_data;
+ scene_data.flip_y = !p_flip_y; // Q: Why is this inverted? Do we assume flip in shadow logic?
scene_data.cam_projection = p_projection;
scene_data.cam_transform = p_transform;
scene_data.view_projection[0] = p_projection;
@@ -1327,7 +1328,7 @@ void RenderForwardMobile::_render_shadow_append(RID p_framebuffer, const PagedAr
render_data.instances = &p_instances;
render_data.render_info = p_render_info;
- _setup_environment(&render_data, true, Vector2(1, 1), !p_flip_y, Color(), false, p_use_pancake, shadow_pass_index);
+ _setup_environment(&render_data, true, Vector2(1, 1), Color(), false, p_use_pancake, shadow_pass_index);
if (get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_DISABLE_LOD) {
scene_data.screen_mesh_lod_threshold = 0.0;
@@ -1415,7 +1416,7 @@ void RenderForwardMobile::_render_material(const Transform3D &p_cam_transform, c
render_data.scene_data = &scene_data;
render_data.instances = &p_instances;
- _setup_environment(&render_data, true, Vector2(1, 1), false, Color());
+ _setup_environment(&render_data, true, Vector2(1, 1), Color());
PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL;
_fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode);
@@ -1460,7 +1461,7 @@ void RenderForwardMobile::_render_uv2(const PagedArray<RenderGeometryInstance *>
render_data.scene_data = &scene_data;
render_data.instances = &p_instances;
- _setup_environment(&render_data, true, Vector2(1, 1), false, Color());
+ _setup_environment(&render_data, true, Vector2(1, 1), Color());
PassMode pass_mode = PASS_MODE_DEPTH_MATERIAL;
_fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode);
@@ -1526,6 +1527,7 @@ void RenderForwardMobile::_render_particle_collider_heightfield(RID p_fb, const
_update_render_base_uniform_set();
RenderSceneDataRD scene_data;
+ scene_data.flip_y = true;
scene_data.cam_projection = p_cam_projection;
scene_data.cam_transform = p_cam_transform;
scene_data.view_projection[0] = p_cam_projection;
@@ -1541,7 +1543,7 @@ void RenderForwardMobile::_render_particle_collider_heightfield(RID p_fb, const
render_data.scene_data = &scene_data;
render_data.instances = &p_instances;
- _setup_environment(&render_data, true, Vector2(1, 1), true, Color(), false, false);
+ _setup_environment(&render_data, true, Vector2(1, 1), Color(), false, false);
PassMode pass_mode = PASS_MODE_SHADOW;
@@ -1974,7 +1976,7 @@ void RenderForwardMobile::_fill_render_list(RenderListType p_render_list, const
}
}
-void RenderForwardMobile::_setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_opaque_render_buffers, bool p_pancake_shadows, int p_index) {
+void RenderForwardMobile::_setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, const Color &p_default_bg_color, bool p_opaque_render_buffers, bool p_pancake_shadows, int p_index) {
RID env = is_environment(p_render_data->environment) ? p_render_data->environment : RID();
RID reflection_probe_instance = p_render_data->reflection_probe.is_valid() ? RendererRD::LightStorage::get_singleton()->reflection_probe_instance_get_probe(p_render_data->reflection_probe) : RID();
@@ -1987,7 +1989,7 @@ void RenderForwardMobile::_setup_environment(const RenderDataRD *p_render_data,
}
}
- p_render_data->scene_data->update_ubo(scene_state.uniform_buffers[p_index], get_debug_draw_mode(), env, reflection_probe_instance, p_render_data->camera_attributes, p_flip_y, p_pancake_shadows, p_screen_size, p_default_bg_color, _render_buffers_get_luminance_multiplier(), p_opaque_render_buffers, false);
+ p_render_data->scene_data->update_ubo(scene_state.uniform_buffers[p_index], get_debug_draw_mode(), env, reflection_probe_instance, p_render_data->camera_attributes, p_pancake_shadows, p_screen_size, p_default_bg_color, _render_buffers_get_luminance_multiplier(), p_opaque_render_buffers, false);
}
/// RENDERING ///
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
index aa1b8f34b2..b0fe552449 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
@@ -197,7 +197,7 @@ private:
void _fill_instance_data(RenderListType p_render_list, uint32_t p_offset = 0, int32_t p_max_elements = -1, bool p_update_buffer = true);
void _fill_render_list(RenderListType p_render_list, const RenderDataRD *p_render_data, PassMode p_pass_mode, bool p_append = false);
- void _setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, bool p_flip_y, const Color &p_default_bg_color, bool p_opaque_render_buffers = false, bool p_pancake_shadows = false, int p_index = 0);
+ void _setup_environment(const RenderDataRD *p_render_data, bool p_no_fog, const Size2i &p_screen_size, const Color &p_default_bg_color, bool p_opaque_render_buffers = false, bool p_pancake_shadows = false, int p_index = 0);
void _setup_lightmaps(const RenderDataRD *p_render_data, const PagedArray<RID> &p_lightmaps, const Transform3D &p_cam_transform);
RID render_base_uniform_set;
diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
index 96bcd72099..0ebed49ee9 100644
--- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
@@ -1124,6 +1124,7 @@ void RendererSceneRenderRD::render_scene(const Ref<RenderSceneBuffers> &p_render
scene_data.camera_visible_layers = p_camera_data->visible_layers;
scene_data.taa_jitter = p_camera_data->taa_jitter;
scene_data.main_cam_transform = p_camera_data->main_transform;
+ scene_data.flip_y = !p_reflection_probe.is_valid();
scene_data.view_count = p_camera_data->view_count;
for (uint32_t v = 0; v < p_camera_data->view_count; v++) {
diff --git a/servers/rendering/renderer_rd/shaders/effects/ss_effects_downsample.glsl b/servers/rendering/renderer_rd/shaders/effects/ss_effects_downsample.glsl
index 4f81e36c58..0332e23993 100644
--- a/servers/rendering/renderer_rd/shaders/effects/ss_effects_downsample.glsl
+++ b/servers/rendering/renderer_rd/shaders/effects/ss_effects_downsample.glsl
@@ -50,7 +50,7 @@ layout(r16f, set = 2, binding = 3) uniform restrict writeonly image2DArray dest_
vec4 screen_space_to_view_space_depth(vec4 p_depth) {
if (params.orthogonal) {
vec4 depth = p_depth * 2.0 - 1.0;
- return ((depth + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / 2.0;
+ return -(depth * (params.z_far - params.z_near) - (params.z_far + params.z_near)) / 2.0;
}
float depth_linearize_mul = params.z_near;
@@ -68,7 +68,7 @@ vec4 screen_space_to_view_space_depth(vec4 p_depth) {
float screen_space_to_view_space_depth(float p_depth) {
if (params.orthogonal) {
float depth = p_depth * 2.0 - 1.0;
- return ((depth + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / (2.0 * params.z_far);
+ return -(depth * (params.z_far - params.z_near) - (params.z_far + params.z_near)) / 2.0;
}
float depth_linearize_mul = params.z_near;
diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp
index dc1e64ddcc..ba8aafda6d 100644
--- a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp
@@ -42,7 +42,11 @@ Transform3D RenderSceneDataRD::get_cam_transform() const {
}
Projection RenderSceneDataRD::get_cam_projection() const {
- return cam_projection;
+ Projection correction;
+ correction.set_depth_correction(flip_y);
+ correction.add_jitter_offset(taa_jitter);
+
+ return correction * cam_projection;
}
uint32_t RenderSceneDataRD::get_view_count() const {
@@ -58,14 +62,18 @@ Vector3 RenderSceneDataRD::get_view_eye_offset(uint32_t p_view) const {
Projection RenderSceneDataRD::get_view_projection(uint32_t p_view) const {
ERR_FAIL_UNSIGNED_INDEX_V(p_view, view_count, Projection());
- return view_projection[p_view];
+ Projection correction;
+ correction.set_depth_correction(flip_y);
+ correction.add_jitter_offset(taa_jitter);
+
+ return correction * view_projection[p_view];
}
RID RenderSceneDataRD::create_uniform_buffer() {
return RD::get_singleton()->uniform_buffer_create(sizeof(UBODATA));
}
-void RenderSceneDataRD::update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p_debug_mode, RID p_env, RID p_reflection_probe_instance, RID p_camera_attributes, bool p_flip_y, bool p_pancake_shadows, const Size2i &p_screen_size, const Color &p_default_bg_color, float p_luminance_multiplier, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier) {
+void RenderSceneDataRD::update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p_debug_mode, RID p_env, RID p_reflection_probe_instance, RID p_camera_attributes, bool p_pancake_shadows, const Size2i &p_screen_size, const Color &p_default_bg_color, float p_luminance_multiplier, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier) {
RendererSceneRenderRD *render_scene_render = RendererSceneRenderRD::get_singleton();
UBODATA ubo_data;
@@ -76,7 +84,7 @@ void RenderSceneDataRD::update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p
UBO &prev_ubo = ubo_data.prev_ubo;
Projection correction;
- correction.set_depth_correction(p_flip_y);
+ correction.set_depth_correction(flip_y);
correction.add_jitter_offset(taa_jitter);
Projection projection = correction * cam_projection;
diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h
index f6785942ed..5579a97792 100644
--- a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h
+++ b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h
@@ -50,6 +50,7 @@ public:
Vector2 taa_jitter;
uint32_t camera_visible_layers;
bool cam_orthogonal = false;
+ bool flip_y = false;
// For billboards to cast correct shadows.
Transform3D main_cam_transform;
@@ -90,7 +91,7 @@ public:
virtual Projection get_view_projection(uint32_t p_view) const override;
RID create_uniform_buffer();
- void update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p_debug_mode, RID p_env, RID p_reflection_probe_instance, RID p_camera_attributes, bool p_flip_y, bool p_pancake_shadows, const Size2i &p_screen_size, const Color &p_default_bg_color, float p_luminance_multiplier, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier);
+ void update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p_debug_mode, RID p_env, RID p_reflection_probe_instance, RID p_camera_attributes, bool p_pancake_shadows, const Size2i &p_screen_size, const Color &p_default_bg_color, float p_luminance_multiplier, bool p_opaque_render_buffers, bool p_apply_alpha_multiplier);
virtual RID get_uniform_buffer() const override;
private:
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index 59f7b3d9e1..c65d3bec95 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -3500,7 +3500,12 @@ Error RenderingDevice::screen_prepare_for_drawing(DisplayServer::WindowID p_scre
framebuffer = driver->swap_chain_acquire_framebuffer(main_queue, it->value, resize_required);
}
- ERR_FAIL_COND_V_MSG(framebuffer.id == 0, FAILED, "Unable to acquire framebuffer.");
+ if (framebuffer.id == 0) {
+ // Some drivers like NVIDIA are fast enough to invalidate the swap chain between resizing and acquisition (GH-94104).
+ // This typically occurs during continuous window resizing operations, especially if done quickly.
+ // Allow this to fail silently since it has no visual consequences.
+ return ERR_CANT_CREATE;
+ }
// Store the framebuffer that will be used next to draw to this screen.
screen_framebuffers[p_screen] = framebuffer;
@@ -5380,7 +5385,7 @@ Error RenderingDevice::initialize(RenderingContextDriver *p_context, DisplayServ
frame = 0;
frames.resize(frame_count);
- max_timestamp_query_elements = 256;
+ max_timestamp_query_elements = GLOBAL_GET("debug/settings/profiler/max_timestamp_query_elements");
device = context->device_get(device_index);
err = driver->initialize(device_index, frame_count);
@@ -5631,7 +5636,7 @@ void RenderingDevice::_free_rids(T &p_owner, const char *p_type) {
void RenderingDevice::capture_timestamp(const String &p_name) {
ERR_FAIL_COND_MSG(draw_list != nullptr && draw_list->state.draw_count > 0, "Capturing timestamps during draw list creation is not allowed. Offending timestamp was: " + p_name);
ERR_FAIL_COND_MSG(compute_list != nullptr && compute_list->state.dispatch_count > 0, "Capturing timestamps during compute list creation is not allowed. Offending timestamp was: " + p_name);
- ERR_FAIL_COND(frames[frame].timestamp_count >= max_timestamp_query_elements);
+ ERR_FAIL_COND_MSG(frames[frame].timestamp_count >= max_timestamp_query_elements, vformat("Tried capturing more timestamps than the configured maximum (%d). You can increase this limit in the project settings under 'Debug/Settings' called 'Max Timestamp Query Elements'.", max_timestamp_query_elements));
draw_graph.add_capture_timestamp(frames[frame].timestamp_pool, frames[frame].timestamp_count);
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index f5e0b811a2..0b1595d988 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -39,6 +39,8 @@
#define HAS_WARNING(flag) (warning_flags & flag)
+int ShaderLanguage::instance_counter = 0;
+
String ShaderLanguage::get_operator_text(Operator p_op) {
static const char *op_names[OP_MAX] = { "==",
"!=",
@@ -10812,17 +10814,16 @@ ShaderLanguage::ShaderLanguage() {
nodes = nullptr;
completion_class = TAG_GLOBAL;
- int idx = 0;
- while (builtin_func_defs[idx].name) {
- if (builtin_func_defs[idx].tag == SubClassTag::TAG_GLOBAL) {
- const StringName &name = StringName(builtin_func_defs[idx].name);
-
- if (!global_func_set.has(name)) {
- global_func_set.insert(name);
+ if (instance_counter == 0) {
+ int idx = 0;
+ while (builtin_func_defs[idx].name) {
+ if (builtin_func_defs[idx].tag == SubClassTag::TAG_GLOBAL) {
+ global_func_set.insert(builtin_func_defs[idx].name);
}
+ idx++;
}
- idx++;
}
+ instance_counter++;
#ifdef DEBUG_ENABLED
warnings_check_map.insert(ShaderWarning::UNUSED_CONSTANT, &used_constants);
@@ -10837,5 +10838,8 @@ ShaderLanguage::ShaderLanguage() {
ShaderLanguage::~ShaderLanguage() {
clear();
- global_func_set.clear();
+ instance_counter--;
+ if (instance_counter == 0) {
+ global_func_set.clear();
+ }
}
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index edac819a1e..076bd8def4 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -800,6 +800,8 @@ public:
static bool is_control_flow_keyword(String p_keyword);
static void get_builtin_funcs(List<String> *r_keywords);
+ static int instance_counter;
+
struct BuiltInInfo {
DataType type = TYPE_VOID;
bool constant = false;
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index dd3491f62c..70b585d683 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -347,6 +347,22 @@ void _get_tbn_from_axis_angle(const Vector3 &p_axis, float p_angle, Vector3 &r_n
r_normal = tbn.rows[2];
}
+AABB _compute_aabb_from_points(const Vector3 *p_data, int p_length) {
+ if (p_length == 0) {
+ return AABB();
+ }
+
+ Vector3 min = p_data[0];
+ Vector3 max = p_data[0];
+
+ for (int i = 1; i < p_length; ++i) {
+ min = min.min(p_data[i]);
+ max = max.max(p_data[i]);
+ }
+
+ return AABB(min, max - min);
+}
+
Error RenderingServer::_surface_set_data(Array p_arrays, uint64_t p_format, uint32_t *p_offsets, uint32_t p_vertex_stride, uint32_t p_normal_stride, uint32_t p_attrib_stride, uint32_t p_skin_stride, Vector<uint8_t> &r_vertex_array, Vector<uint8_t> &r_attrib_array, Vector<uint8_t> &r_skin_array, int p_vertex_array_len, Vector<uint8_t> &r_index_array, int p_index_array_len, AABB &r_aabb, Vector<AABB> &r_bone_aabb, Vector4 &r_uv_scale) {
uint8_t *vw = r_vertex_array.ptrw();
uint8_t *aw = r_attrib_array.ptrw();
@@ -440,18 +456,10 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint64_t p_format, uint
const Vector3 *src = array.ptr();
- r_aabb = AABB();
+ r_aabb = _compute_aabb_from_points(src, p_vertex_array_len);
+ r_aabb.size = r_aabb.size.max(SMALL_VEC3);
if (p_format & ARRAY_FLAG_COMPRESS_ATTRIBUTES) {
- // First we need to generate the AABB for the entire surface.
- for (int i = 0; i < p_vertex_array_len; i++) {
- if (i == 0) {
- r_aabb = AABB(src[i], SMALL_VEC3);
- } else {
- r_aabb.expand_to(src[i]);
- }
- }
-
if (!(p_format & RS::ARRAY_FORMAT_NORMAL)) {
// Early out if we are only setting vertex positions.
for (int i = 0; i < p_vertex_array_len; i++) {
@@ -592,12 +600,6 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint64_t p_format, uint
float vector[3] = { (float)src[i].x, (float)src[i].y, (float)src[i].z };
memcpy(&vw[p_offsets[ai] + i * p_vertex_stride], vector, sizeof(float) * 3);
-
- if (i == 0) {
- r_aabb = AABB(src[i], SMALL_VEC3);
- } else {
- r_aabb.expand_to(src[i]);
- }
}
}
}
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index e15dba4353..693c822488 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -44,14 +44,6 @@
// Helper macros for code outside of the rendering server, but that is
// called by the rendering server.
#ifdef DEBUG_ENABLED
-#define ERR_ON_RENDER_THREAD \
- RenderingServer *rendering_server = RenderingServer::get_singleton(); \
- ERR_FAIL_NULL(rendering_server); \
- ERR_FAIL_COND(rendering_server->is_on_render_thread());
-#define ERR_ON_RENDER_THREAD_V(m_ret) \
- RenderingServer *rendering_server = RenderingServer::get_singleton(); \
- ERR_FAIL_NULL_V(rendering_server, m_ret); \
- ERR_FAIL_COND_V(rendering_server->is_on_render_thread(), m_ret);
#define ERR_NOT_ON_RENDER_THREAD \
RenderingServer *rendering_server = RenderingServer::get_singleton(); \
ERR_FAIL_NULL(rendering_server); \
@@ -61,8 +53,6 @@
ERR_FAIL_NULL_V(rendering_server, m_ret); \
ERR_FAIL_COND_V(!rendering_server->is_on_render_thread(), m_ret);
#else
-#define ERR_ON_RENDER_THREAD
-#define ERR_ON_RENDER_THREAD_V(m_ret)
#define ERR_NOT_ON_RENDER_THREAD
#define ERR_NOT_ON_RENDER_THREAD_V(m_ret)
#endif
diff --git a/tests/core/math/test_basis.h b/tests/core/math/test_basis.h
index a9bc2e9b99..f8c5ef279d 100644
--- a/tests/core/math/test_basis.h
+++ b/tests/core/math/test_basis.h
@@ -93,9 +93,9 @@ void test_rotation(Vector3 deg_original_euler, EulerOrder rot_order) {
Basis res = to_rotation.inverse() * rotation_from_computed_euler;
- CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.1, vformat("Fail due to X %s\n", String(res.get_column(0))).utf8().ptr());
- CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.1, vformat("Fail due to Y %s\n", String(res.get_column(1))).utf8().ptr());
- CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Fail due to Z %s\n", String(res.get_column(2))).utf8().ptr());
+ CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.1, vformat("Fail due to X %s\n", String(res.get_column(0))));
+ CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.1, vformat("Fail due to Y %s\n", String(res.get_column(1))));
+ CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Fail due to Z %s\n", String(res.get_column(2))));
// Double check `to_rotation` decomposing with XYZ rotation order.
const Vector3 euler_xyz_from_rotation = to_rotation.get_euler(EulerOrder::XYZ);
@@ -103,13 +103,13 @@ void test_rotation(Vector3 deg_original_euler, EulerOrder rot_order) {
res = to_rotation.inverse() * rotation_from_xyz_computed_euler;
- CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to X %s\n", String(res.get_column(0))).utf8().ptr());
- CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to Y %s\n", String(res.get_column(1))).utf8().ptr());
- CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to Z %s\n", String(res.get_column(2))).utf8().ptr());
+ CHECK_MESSAGE((res.get_column(0) - Vector3(1.0, 0.0, 0.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to X %s\n", String(res.get_column(0))));
+ CHECK_MESSAGE((res.get_column(1) - Vector3(0.0, 1.0, 0.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to Y %s\n", String(res.get_column(1))));
+ CHECK_MESSAGE((res.get_column(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Double check with XYZ rot order failed, due to Z %s\n", String(res.get_column(2))));
- INFO(vformat("Rotation order: %s\n.", get_rot_order_name(rot_order)).utf8().ptr());
- INFO(vformat("Original Rotation: %s\n", String(deg_original_euler)).utf8().ptr());
- INFO(vformat("Quaternion to rotation order: %s\n", String(rad2deg(euler_from_rotation))).utf8().ptr());
+ INFO(vformat("Rotation order: %s\n.", get_rot_order_name(rot_order)));
+ INFO(vformat("Original Rotation: %s\n", String(deg_original_euler)));
+ INFO(vformat("Quaternion to rotation order: %s\n", String(rad2deg(euler_from_rotation))));
}
TEST_CASE("[Basis] Euler conversions") {
diff --git a/tests/core/object/test_class_db.h b/tests/core/object/test_class_db.h
index 358bbc08a3..c1aa39031d 100644
--- a/tests/core/object/test_class_db.h
+++ b/tests/core/object/test_class_db.h
@@ -405,7 +405,7 @@ void validate_argument(const Context &p_context, const ExposedClass &p_class, co
err_msg += " " + type_error_msg;
}
- TEST_COND(!arg_defval_assignable_to_type, err_msg.utf8().get_data());
+ TEST_COND(!arg_defval_assignable_to_type, err_msg);
}
}
@@ -590,7 +590,7 @@ void add_exposed_classes(Context &r_context) {
exposed_class.name, method.name);
TEST_FAIL_COND_WARN(
(exposed_class.name != r_context.names_cache.object_class || String(method.name) != "free"),
- warn_msg.utf8().get_data());
+ warn_msg);
} else if (return_info.type == Variant::INT && return_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) {
method.return_type.name = return_info.class_name;
@@ -720,7 +720,7 @@ void add_exposed_classes(Context &r_context) {
"Signal name conflicts with %s: '%s.%s.",
method_conflict ? "method" : "property", class_name, signal.name);
TEST_FAIL_COND((method_conflict || exposed_class.find_method_by_name(signal.name)),
- warn_msg.utf8().get_data());
+ warn_msg);
exposed_class.signals_.push_back(signal);
}
diff --git a/thirdparty/README.md b/thirdparty/README.md
index 4572687be2..5bc76026c7 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -882,7 +882,7 @@ instead of `miniz.h` as an external dependency.
## thorvg
- Upstream: https://github.com/thorvg/thorvg
-- Version: 0.14.0 (ae4e9d003c93325f1eba64319fa9852a0d764b4c, 2024)
+- Version: 0.14.1 (70b2f2dad158316dd08166d613b425248b36fd27, 2024)
- License: MIT
Files extracted from upstream source:
diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h
index 6218c18e68..4be7e3936d 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.0"
+#define THORVG_VERSION_STRING "0.14.1"
#endif
diff --git a/thirdparty/thorvg/inc/thorvg.h b/thirdparty/thorvg/inc/thorvg.h
index 0b7e9771b9..20f1942a57 100644
--- a/thirdparty/thorvg/inc/thorvg.h
+++ b/thirdparty/thorvg/inc/thorvg.h
@@ -631,6 +631,8 @@ public:
* The Canvas rendering can be performed asynchronously. To make sure that rendering is finished,
* the sync() must be called after the draw() regardless of threading.
*
+ * @retval Result::InsufficientCondition: The canvas is either already in sync condition or in a damaged condition (a draw is required before syncing).
+ *
* @see Canvas::draw()
*/
virtual Result sync() noexcept;
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
index 231410cdac..05cbdc7f3a 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h
@@ -23,6 +23,7 @@
#ifndef _TVG_SW_COMMON_H_
#define _TVG_SW_COMMON_H_
+#include <algorithm>
#include "tvgCommon.h"
#include "tvgRender.h"
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp
index be1662daeb..bd0b5ffdcb 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwFill.cpp
@@ -63,6 +63,66 @@ static void _calculateCoefficients(const SwFill* fill, uint32_t x, uint32_t y, f
}
+static uint32_t _estimateAAMargin(const Fill* fdata)
+{
+ constexpr float marginScalingFactor = 800.0f;
+ if (fdata->identifier() == TVG_CLASS_ID_RADIAL) {
+ auto radius = P(static_cast<const RadialGradient*>(fdata))->r;
+ return mathZero(radius) ? 0 : static_cast<uint32_t>(marginScalingFactor / radius);
+ }
+ auto grad = P(static_cast<const LinearGradient*>(fdata));
+ Point p1 {grad->x1, grad->y1};
+ Point p2 {grad->x2, grad->y2};
+ auto length = mathLength(&p1, &p2);
+ return mathZero(length) ? 0 : static_cast<uint32_t>(marginScalingFactor / length);
+}
+
+
+static void _adjustAAMargin(uint32_t& iMargin, uint32_t index)
+{
+ constexpr float threshold = 0.1f;
+ constexpr uint32_t iMarginMax = 40;
+
+ auto iThreshold = static_cast<uint32_t>(index * threshold);
+ if (iMargin > iThreshold) iMargin = iThreshold;
+ if (iMargin > iMarginMax) iMargin = iMarginMax;
+}
+
+
+static inline uint32_t _alphaUnblend(uint32_t c)
+{
+ auto a = (c >> 24);
+ if (a == 255 || a == 0) return c;
+ auto invA = 255.0f / static_cast<float>(a);
+ auto c0 = static_cast<uint8_t>(static_cast<float>((c >> 16) & 0xFF) * invA);
+ auto c1 = static_cast<uint8_t>(static_cast<float>((c >> 8) & 0xFF) * invA);
+ auto c2 = static_cast<uint8_t>(static_cast<float>(c & 0xFF) * invA);
+
+ return (a << 24) | (c0 << 16) | (c1 << 8) | c2;
+}
+
+
+static void _applyAA(const SwFill* fill, uint32_t begin, uint32_t end)
+{
+ if (begin == 0 || end == 0) return;
+
+ auto i = GRADIENT_STOP_SIZE - end;
+ auto rgbaEnd = _alphaUnblend(fill->ctable[i]);
+ auto rgbaBegin = _alphaUnblend(fill->ctable[begin]);
+
+ auto dt = 1.0f / (begin + end + 1.0f);
+ float t = dt;
+ while (i != begin) {
+ auto dist = 255 - static_cast<int32_t>(255 * t);
+ auto color = INTERPOLATE(rgbaEnd, rgbaBegin, dist);
+ fill->ctable[i++] = ALPHA_BLEND((color | 0xff000000), (color >> 24));
+
+ if (i == GRADIENT_STOP_SIZE) i = 0;
+ t += dt;
+ }
+}
+
+
static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* surface, uint8_t opacity)
{
if (!fill->ctable) {
@@ -88,6 +148,11 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface*
auto pos = 1.5f * inc;
uint32_t i = 0;
+ //If repeat is true, anti-aliasing must be applied between the last and the first colors.
+ auto repeat = fill->spread == FillSpread::Repeat;
+ uint32_t iAABegin = repeat ? _estimateAAMargin(fdata) : 0;
+ uint32_t iAAEnd = 0;
+
fill->ctable[i++] = ALPHA_BLEND(rgba | 0xff000000, a);
while (pos <= pColors->offset) {
@@ -97,6 +162,11 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface*
}
for (uint32_t j = 0; j < cnt - 1; ++j) {
+ if (repeat && j == cnt - 2 && iAAEnd == 0) {
+ iAAEnd = iAABegin;
+ _adjustAAMargin(iAAEnd, GRADIENT_STOP_SIZE - i);
+ }
+
auto curr = colors + j;
auto next = curr + 1;
auto delta = 1.0f / (next->offset - curr->offset);
@@ -118,14 +188,18 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface*
}
rgba = rgba2;
a = a2;
+
+ if (repeat && j == 0) _adjustAAMargin(iAABegin, i - 1);
}
rgba = ALPHA_BLEND((rgba | 0xff000000), a);
for (; i < GRADIENT_STOP_SIZE; ++i)
fill->ctable[i] = rgba;
- //Make sure the last color stop is represented at the end of the table
- fill->ctable[GRADIENT_STOP_SIZE - 1] = rgba;
+ //For repeat fill spread apply anti-aliasing between the last and first colors,
+ //othewise make sure the last color stop is represented at the end of the table.
+ if (repeat) _applyAA(fill, iAABegin, iAAEnd);
+ else fill->ctable[GRADIENT_STOP_SIZE - 1] = rgba;
return true;
}
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp
index 04f36c727f..b3507acdc3 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp
@@ -1087,6 +1087,7 @@ static bool _rasterDirectScaledMaskedImage(SwSurface* surface, const SwImage* im
static bool _rasterScaledMaskedImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity)
{
+ TVGERR("SW_ENGINE", "Not supported ScaledMaskedImage!");
#if 0 //Enable it when GRAYSCALE image is supported
TVGLOG("SW_ENGINE", "Scaled Masked(%d) Image [Region: %lu %lu %lu %lu]", (int)surface->compositor->method, region.min.x, region.min.y, region.max.x - region.min.x, region.max.y - region.min.y);
@@ -1100,6 +1101,11 @@ static bool _rasterScaledMaskedImage(SwSurface* surface, const SwImage* image, c
static bool _rasterScaledMattedImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity)
{
+ if (surface->channelSize == sizeof(uint8_t)) {
+ TVGERR("SW_ENGINE", "Not supported grayscale scaled matted image!");
+ return false;
+ }
+
auto dbuffer = surface->buf32 + (region.min.y * surface->stride + region.min.x);
auto csize = surface->compositor->image.channelSize;
auto cbuffer = surface->compositor->image.buf8 + (region.min.y * surface->compositor->image.stride + region.min.x) * csize;
@@ -1130,6 +1136,11 @@ static bool _rasterScaledMattedImage(SwSurface* surface, const SwImage* image, c
static bool _rasterScaledBlendingImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity)
{
+ if (surface->channelSize == sizeof(uint8_t)) {
+ TVGERR("SW_ENGINE", "Not supported grayscale scaled blending image!");
+ return false;
+ }
+
auto dbuffer = surface->buf32 + (region.min.y * surface->stride + region.min.x);
auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler;
auto sampleSize = _sampleSize(image->scale);
@@ -1152,19 +1163,33 @@ static bool _rasterScaledBlendingImage(SwSurface* surface, const SwImage* image,
static bool _rasterScaledImage(SwSurface* surface, const SwImage* image, const Matrix* itransform, const SwBBox& region, uint8_t opacity)
{
- auto dbuffer = surface->buf32 + (region.min.y * surface->stride + region.min.x);
auto scaleMethod = image->scale < DOWN_SCALE_TOLERANCE ? _interpDownScaler : _interpUpScaler;
auto sampleSize = _sampleSize(image->scale);
int32_t miny = 0, maxy = 0;
- for (auto y = region.min.y; y < region.max.y; ++y, dbuffer += surface->stride) {
- SCALED_IMAGE_RANGE_Y(y)
- auto dst = dbuffer;
- for (auto x = region.min.x; x < region.max.x; ++x, ++dst) {
- SCALED_IMAGE_RANGE_X
- auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize);
- if (opacity < 255) src = ALPHA_BLEND(src, opacity);
- *dst = src + ALPHA_BLEND(*dst, IA(src));
+ //32bits channels
+ if (surface->channelSize == sizeof(uint32_t)) {
+ auto buffer = surface->buf32 + (region.min.y * surface->stride + region.min.x);
+ for (auto y = region.min.y; y < region.max.y; ++y, buffer += surface->stride) {
+ SCALED_IMAGE_RANGE_Y(y)
+ auto dst = buffer;
+ for (auto x = region.min.x; x < region.max.x; ++x, ++dst) {
+ SCALED_IMAGE_RANGE_X
+ auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize);
+ if (opacity < 255) src = ALPHA_BLEND(src, opacity);
+ *dst = src + ALPHA_BLEND(*dst, IA(src));
+ }
+ }
+ } else if (surface->channelSize == sizeof(uint8_t)) {
+ auto buffer = surface->buf8 + (region.min.y * surface->stride + region.min.x);
+ for (auto y = region.min.y; y < region.max.y; ++y, buffer += surface->stride) {
+ SCALED_IMAGE_RANGE_Y(y)
+ auto dst = buffer;
+ for (auto x = region.min.x; x < region.max.x; ++x, ++dst) {
+ SCALED_IMAGE_RANGE_X
+ auto src = scaleMethod(image->buf32, image->stride, image->w, image->h, sx, sy, miny, maxy, sampleSize);
+ *dst = MULTIPLY(A(src), opacity);
+ }
}
}
return true;
@@ -1173,11 +1198,6 @@ 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)
{
- if (surface->channelSize == sizeof(uint8_t)) {
- TVGERR("SW_ENGINE", "Not supported grayscale Textmap polygon mesh!");
- return false;
- }
-
Matrix itransform;
if (transform) {
diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
index 0a3f5ef7e7..350f333405 100644
--- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
+++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp
@@ -20,6 +20,7 @@
* SOFTWARE.
*/
+#include <algorithm>
#include "tvgMath.h"
#include "tvgSwCommon.h"
#include "tvgTaskScheduler.h"
@@ -86,7 +87,7 @@ struct SwShapeTask : SwTask
Additionally, the stroke style should not be dashed. */
bool antialiasing(float strokeWidth)
{
- return strokeWidth < 2.0f || rshape->stroke->dashCnt > 0 || rshape->stroke->strokeFirst || rshape->strokeTrim();
+ return strokeWidth < 2.0f || rshape->stroke->dashCnt > 0 || rshape->stroke->strokeFirst || rshape->strokeTrim() || rshape->stroke->color[3] < 255;;
}
float validStrokeWidth()
diff --git a/thirdparty/thorvg/src/renderer/tvgCanvas.h b/thirdparty/thorvg/src/renderer/tvgCanvas.h
index 9d216e2f30..81fd1b7d6f 100644
--- a/thirdparty/thorvg/src/renderer/tvgCanvas.h
+++ b/thirdparty/thorvg/src/renderer/tvgCanvas.h
@@ -129,7 +129,7 @@ struct Canvas::Impl
return Result::Success;
}
- return Result::InsufficientCondition;
+ return Result::Unknown;
}
Result viewport(int32_t x, int32_t y, int32_t w, int32_t h)
diff --git a/thirdparty/thorvg/src/renderer/tvgRender.h b/thirdparty/thorvg/src/renderer/tvgRender.h
index 8f28d37dbc..a915d68fec 100644
--- a/thirdparty/thorvg/src/renderer/tvgRender.h
+++ b/thirdparty/thorvg/src/renderer/tvgRender.h
@@ -103,7 +103,7 @@ struct RenderRegion
void intersect(const RenderRegion& rhs);
void add(const RenderRegion& rhs);
- bool operator==(const RenderRegion& rhs)
+ bool operator==(const RenderRegion& rhs) const
{
if (x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h) return true;
return false;
diff --git a/thirdparty/thorvg/src/renderer/tvgShape.h b/thirdparty/thorvg/src/renderer/tvgShape.h
index c45995a64d..ecc58b6cc0 100644
--- a/thirdparty/thorvg/src/renderer/tvgShape.h
+++ b/thirdparty/thorvg/src/renderer/tvgShape.h
@@ -296,6 +296,7 @@ struct Shape::Impl
if (!rs.stroke) rs.stroke = new RenderStroke();
if (rs.stroke->fill && rs.stroke->fill != p) delete(rs.stroke->fill);
rs.stroke->fill = p;
+ rs.stroke->color[3] = 0;
flag |= RenderUpdateFlag::Stroke;
flag |= RenderUpdateFlag::GradientStroke;
diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh
index d08158bfe7..e964e5ab6d 100755
--- a/thirdparty/thorvg/update-thorvg.sh
+++ b/thirdparty/thorvg/update-thorvg.sh
@@ -1,6 +1,6 @@
#!/bin/bash -e
-VERSION=0.14.0
+VERSION=0.14.1
cd thirdparty/thorvg/ || true
rm -rf AUTHORS LICENSE inc/ src/ *.zip *.tar.gz tmp/