summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/config/engine.cpp8
-rw-r--r--core/config/engine.h5
-rw-r--r--core/config/project_settings.cpp42
-rw-r--r--core/core_bind.cpp30
-rw-r--r--core/core_bind.h2
-rw-r--r--core/crypto/crypto.cpp2
-rw-r--r--core/crypto/crypto_core.cpp4
-rw-r--r--core/debugger/engine_debugger.cpp18
-rw-r--r--core/debugger/remote_debugger.cpp4
-rw-r--r--core/debugger/remote_debugger_peer.cpp2
-rw-r--r--core/extension/extension_api_dump.cpp10
-rw-r--r--core/extension/gdextension.cpp30
-rw-r--r--core/extension/gdextension_interface.cpp2
-rw-r--r--core/extension/gdextension_library_loader.cpp10
-rw-r--r--core/extension/gdextension_manager.cpp2
-rw-r--r--core/input/input.cpp108
-rw-r--r--core/input/input.h10
-rw-r--r--core/input/input_map.cpp2
-rw-r--r--core/io/dir_access.cpp16
-rw-r--r--core/io/dir_access.h4
-rw-r--r--core/io/file_access.cpp20
-rw-r--r--core/io/file_access_compressed.cpp2
-rw-r--r--core/io/file_access_encrypted.cpp2
-rw-r--r--core/io/file_access_memory.cpp2
-rw-r--r--core/io/file_access_pack.cpp10
-rw-r--r--core/io/file_access_zip.cpp4
-rw-r--r--core/io/http_client.cpp4
-rw-r--r--core/io/image.cpp52
-rw-r--r--core/io/image_loader.cpp4
-rw-r--r--core/io/ip.cpp4
-rw-r--r--core/io/json.cpp2
-rw-r--r--core/io/marshalls.cpp8
-rw-r--r--core/io/pck_packer.cpp2
-rw-r--r--core/io/plist.cpp2
-rw-r--r--core/io/remote_filesystem_client.cpp14
-rw-r--r--core/io/resource.cpp2
-rw-r--r--core/io/resource_format_binary.cpp56
-rw-r--r--core/io/resource_importer.cpp4
-rw-r--r--core/io/resource_loader.cpp6
-rw-r--r--core/io/resource_saver.cpp2
-rw-r--r--core/io/translation_loader_po.cpp30
-rw-r--r--core/io/xml_parser.cpp4
-rw-r--r--core/math/expression.cpp2
-rw-r--r--core/math/rect2.cpp2
-rw-r--r--core/math/vector2.cpp2
-rw-r--r--core/math/vector3.cpp2
-rw-r--r--core/object/class_db.cpp88
-rw-r--r--core/object/object.cpp40
-rw-r--r--core/object/script_language.cpp6
-rw-r--r--core/object/undo_redo.cpp2
-rw-r--r--core/os/os.cpp2
-rw-r--r--core/string/node_path.cpp2
-rw-r--r--core/string/translation_po.cpp10
-rw-r--r--core/string/ustring.cpp28
-rw-r--r--core/string/ustring.h1
-rw-r--r--core/variant/array.cpp14
-rw-r--r--core/variant/container_type_validate.h10
-rw-r--r--core/variant/variant.cpp2
-rw-r--r--core/variant/variant_construct.cpp2
-rw-r--r--core/variant/variant_utility.cpp2
-rw-r--r--doc/classes/ClassDB.xml2
-rw-r--r--doc/classes/DisplayServer.xml16
-rw-r--r--doc/classes/EditorFeatureProfile.xml5
-rw-r--r--doc/classes/FileDialog.xml4
-rw-r--r--doc/classes/ImporterMesh.xml3
-rw-r--r--drivers/gles3/shader_gles3.cpp4
-rw-r--r--drivers/gles3/shaders/stdlib_inc.glsl40
-rw-r--r--drivers/gles3/storage/config.cpp7
-rw-r--r--drivers/gles3/storage/config.h9
-rw-r--r--editor/debugger/editor_debugger_node.cpp8
-rw-r--r--editor/debugger/editor_debugger_node.h8
-rw-r--r--editor/debugger/editor_debugger_tree.cpp38
-rw-r--r--editor/debugger/editor_debugger_tree.h3
-rw-r--r--editor/debugger/script_editor_debugger.cpp89
-rw-r--r--editor/editor_feature_profile.cpp4
-rw-r--r--editor/editor_feature_profile.h1
-rw-r--r--editor/editor_file_system.cpp10
-rw-r--r--editor/editor_file_system.h2
-rw-r--r--editor/editor_main_screen.h1
-rw-r--r--editor/editor_node.cpp12
-rw-r--r--editor/filesystem_dock.cpp3
-rw-r--r--editor/filesystem_dock.h1
-rw-r--r--editor/icons/2DNodes.svg1
-rw-r--r--editor/icons/Camera.svg1
-rw-r--r--editor/icons/Game.svg1
-rw-r--r--editor/icons/NextFrame.svg1
-rw-r--r--editor/import/3d/resource_importer_obj.cpp2
-rw-r--r--editor/import/3d/resource_importer_scene.cpp14
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp43
-rw-r--r--editor/plugins/canvas_item_editor_plugin.h4
-rw-r--r--editor/plugins/game_view_plugin.cpp480
-rw-r--r--editor/plugins/game_view_plugin.h154
-rw-r--r--editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp15
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp96
-rw-r--r--editor/plugins/node_3d_editor_plugin.h10
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp23
-rw-r--r--editor/plugins/visual_shader_editor_plugin.h6
-rw-r--r--editor/themes/editor_theme_manager.cpp6
-rw-r--r--misc/extension_api_validation/4.3-stable.expected8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd30
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd30
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_arrays.out26
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/number_separators.out12
-rw-r--r--modules/gdscript/tests/scripts/parser/features/operator_assign.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out12
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out8
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out16
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/stringify.out22
-rw-r--r--modules/gdscript/tests/scripts/utils.notest.gd1
-rw-r--r--modules/mono/editor/bindings_generator.cpp110
-rw-r--r--platform/android/display_server_android.cpp16
-rw-r--r--platform/android/display_server_android.h4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt11
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotLib.java5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/FilePicker.kt160
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt72
-rw-r--r--platform/android/java_godot_lib_jni.cpp16
-rw-r--r--platform/android/java_godot_lib_jni.h1
-rw-r--r--platform/android/java_godot_wrapper.cpp24
-rw-r--r--platform/android/java_godot_wrapper.h2
-rw-r--r--platform/ios/display_server_ios.mm1
-rw-r--r--platform/linuxbsd/wayland/display_server_wayland.cpp3
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp1
-rw-r--r--platform/macos/display_server_macos.mm1
-rw-r--r--platform/web/display_server_web.cpp1
-rw-r--r--platform/web/emscripten_helpers.py20
-rw-r--r--platform/windows/display_server_windows.cpp1
-rw-r--r--scene/2d/camera_2d.cpp1
-rw-r--r--scene/2d/gpu_particles_2d.cpp2
-rw-r--r--scene/2d/light_occluder_2d.cpp1
-rw-r--r--scene/2d/navigation_agent_2d.cpp8
-rw-r--r--scene/2d/navigation_obstacle_2d.cpp8
-rw-r--r--scene/2d/touch_screen_button.cpp1
-rw-r--r--scene/3d/camera_3d.cpp1
-rw-r--r--scene/3d/gpu_particles_3d.cpp2
-rw-r--r--scene/3d/navigation_agent_3d.cpp8
-rw-r--r--scene/3d/navigation_obstacle_3d.cpp8
-rw-r--r--scene/audio/audio_stream_player_internal.cpp8
-rw-r--r--scene/debugger/scene_debugger.cpp1192
-rw-r--r--scene/debugger/scene_debugger.h161
-rw-r--r--scene/gui/code_edit.cpp2
-rw-r--r--scene/gui/file_dialog.cpp27
-rw-r--r--scene/gui/file_dialog.h4
-rw-r--r--scene/gui/video_stream_player.cpp8
-rw-r--r--scene/main/node.cpp13
-rw-r--r--scene/main/node.h3
-rw-r--r--scene/main/scene_tree.cpp27
-rw-r--r--scene/main/scene_tree.h3
-rw-r--r--scene/main/viewport.cpp83
-rw-r--r--scene/main/viewport.h8
-rw-r--r--scene/resources/3d/convex_polygon_shape_3d.cpp2
-rw-r--r--scene/resources/3d/importer_mesh.cpp294
-rw-r--r--scene/resources/3d/importer_mesh.h5
-rw-r--r--scene/resources/visual_shader.cpp2
-rw-r--r--scu_builders.py17
-rw-r--r--servers/display_server.cpp1
-rw-r--r--servers/display_server.h1
-rw-r--r--tests/core/math/test_aabb.h2
-rw-r--r--tests/core/math/test_color.h2
-rw-r--r--tests/core/math/test_rect2.h2
-rw-r--r--tests/core/string/test_string.h24
-rw-r--r--tests/core/variant/test_variant_utility.h2
169 files changed, 3384 insertions, 1090 deletions
diff --git a/core/config/engine.cpp b/core/config/engine.cpp
index 2735a1aa67..bcec1a0ad4 100644
--- a/core/config/engine.cpp
+++ b/core/config/engine.cpp
@@ -119,6 +119,10 @@ void Engine::set_time_scale(double p_scale) {
}
double Engine::get_time_scale() const {
+ return freeze_time_scale ? 0 : _time_scale;
+}
+
+double Engine::get_unfrozen_time_scale() const {
return _time_scale;
}
@@ -457,6 +461,10 @@ bool Engine::notify_frame_server_synced() {
return server_syncs > SERVER_SYNC_FRAME_COUNT_WARNING;
}
+void Engine::set_freeze_time_scale(bool p_frozen) {
+ freeze_time_scale = p_frozen;
+}
+
Engine::Engine() {
singleton = this;
}
diff --git a/core/config/engine.h b/core/config/engine.h
index ec089923b3..737f0eb87e 100644
--- a/core/config/engine.h
+++ b/core/config/engine.h
@@ -101,6 +101,8 @@ private:
int server_syncs = 0;
bool frame_server_synced = false;
+ bool freeze_time_scale = false;
+
public:
static Engine *get_singleton();
@@ -132,6 +134,7 @@ public:
void set_time_scale(double p_scale);
double get_time_scale() const;
+ double get_unfrozen_time_scale() const;
void set_print_to_stdout(bool p_enabled);
bool is_printing_to_stdout() const;
@@ -202,6 +205,8 @@ public:
void increment_frames_drawn();
bool notify_frame_server_synced();
+ void set_freeze_time_scale(bool p_frozen);
+
Engine();
virtual ~Engine();
};
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index 5ac7f05a76..08ce6c0e07 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -216,36 +216,36 @@ String ProjectSettings::localize_path(const String &p_path) const {
}
void ProjectSettings::set_initial_value(const String &p_name, const Variant &p_value) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
// Duplicate so that if value is array or dictionary, changing the setting will not change the stored initial value.
props[p_name].initial = p_value.duplicate();
}
void ProjectSettings::set_restart_if_changed(const String &p_name, bool p_restart) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props[p_name].restart_if_changed = p_restart;
}
void ProjectSettings::set_as_basic(const String &p_name, bool p_basic) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props[p_name].basic = p_basic;
}
void ProjectSettings::set_as_internal(const String &p_name, bool p_internal) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props[p_name].internal = p_internal;
}
void ProjectSettings::set_ignore_value_in_docs(const String &p_name, bool p_ignore) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
#ifdef DEBUG_METHODS_ENABLED
props[p_name].ignore_value_in_docs = p_ignore;
#endif
}
bool ProjectSettings::get_ignore_value_in_docs(const String &p_name) const {
- ERR_FAIL_COND_V_MSG(!props.has(p_name), false, "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_V_MSG(!props.has(p_name), false, vformat("Request for nonexistent project setting: '%s'.", p_name));
#ifdef DEBUG_METHODS_ENABLED
return props[p_name].ignore_value_in_docs;
#else
@@ -373,7 +373,7 @@ Variant ProjectSettings::get_setting_with_override(const StringName &p_name) con
}
if (!props.has(name)) {
- WARN_PRINT("Property not found: " + String(name));
+ WARN_PRINT(vformat("Property not found: '%s'.", String(name)));
return Variant();
}
return props[name].variant;
@@ -567,7 +567,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
if (!p_main_pack.is_empty()) {
bool ok = _load_resource_pack(p_main_pack);
- ERR_FAIL_COND_V_MSG(!ok, ERR_CANT_OPEN, "Cannot open resource pack '" + p_main_pack + "'.");
+ ERR_FAIL_COND_V_MSG(!ok, ERR_CANT_OPEN, vformat("Cannot open resource pack '%s'.", p_main_pack));
Error err = _load_settings_text_or_binary("res://project.godot", "res://project.binary");
if (err == OK && !p_ignore_override) {
@@ -646,7 +646,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
// or, if requested (`p_upwards`) in parent directories.
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- ERR_FAIL_COND_V_MSG(d.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(d.is_null(), ERR_CANT_CREATE, vformat("Cannot create DirAccess for path '%s'.", p_path));
d->change_dir(p_path);
String current_dir = d->get_current_dir();
@@ -750,7 +750,7 @@ Error ProjectSettings::_load_settings_binary(const String &p_path) {
f->get_buffer(d.ptrw(), vlen);
Variant value;
err = decode_variant(value, d.ptr(), d.size(), nullptr, true);
- ERR_CONTINUE_MSG(err != OK, "Error decoding property: " + key + ".");
+ ERR_CONTINUE_MSG(err != OK, vformat("Error decoding property: '%s'.", key));
set(key, value);
}
@@ -792,7 +792,7 @@ Error ProjectSettings::_load_settings_text(const String &p_path) {
last_save_time = FileAccess::get_modified_time(get_resource_path().path_join("project.godot"));
return OK;
}
- ERR_FAIL_COND_V_MSG(err != OK, err, "Error parsing " + p_path + " at line " + itos(lines) + ": " + error_text + " File might be corrupted.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Error parsing '%s' at line %d: %s File might be corrupted.", p_path, lines, error_text));
if (!assign.is_empty()) {
if (section.is_empty() && assign == "config_version") {
@@ -818,7 +818,7 @@ Error ProjectSettings::_load_settings_text_or_binary(const String &p_text_path,
return OK;
} else if (err != ERR_FILE_NOT_FOUND) {
// If the file exists but can't be loaded, we want to know it.
- ERR_PRINT("Couldn't load file '" + p_bin_path + "', error code " + itos(err) + ".");
+ ERR_PRINT(vformat("Couldn't load file '%s', error code %d.", p_bin_path, err));
}
// Fallback to text-based project.godot file if binary was not found.
@@ -826,7 +826,7 @@ Error ProjectSettings::_load_settings_text_or_binary(const String &p_text_path,
if (err == OK) {
return OK;
} else if (err != ERR_FILE_NOT_FOUND) {
- ERR_PRINT("Couldn't load file '" + p_text_path + "', error code " + itos(err) + ".");
+ ERR_PRINT(vformat("Couldn't load file '%s', error code %d.", p_text_path, err));
}
return err;
@@ -840,17 +840,17 @@ Error ProjectSettings::load_custom(const String &p_path) {
}
int ProjectSettings::get_order(const String &p_name) const {
- ERR_FAIL_COND_V_MSG(!props.has(p_name), -1, "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_V_MSG(!props.has(p_name), -1, vformat("Request for nonexistent project setting: '%s'.", p_name));
return props[p_name].order;
}
void ProjectSettings::set_order(const String &p_name, int p_order) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props[p_name].order = p_order;
}
void ProjectSettings::set_builtin_order(const String &p_name) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
if (props[p_name].order >= NO_BUILTIN_ORDER_BASE) {
props[p_name].order = last_builtin_order++;
}
@@ -858,12 +858,12 @@ void ProjectSettings::set_builtin_order(const String &p_name) {
bool ProjectSettings::is_builtin_setting(const String &p_name) const {
// Return true because a false negative is worse than a false positive.
- ERR_FAIL_COND_V_MSG(!props.has(p_name), true, "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_V_MSG(!props.has(p_name), true, vformat("Request for nonexistent project setting: '%s'.", p_name));
return props[p_name].order < NO_BUILTIN_ORDER_BASE;
}
void ProjectSettings::clear(const String &p_name) {
- ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+ ERR_FAIL_COND_MSG(!props.has(p_name), vformat("Request for nonexistent project setting: '%s'.", p_name));
props.erase(p_name);
}
@@ -878,7 +878,7 @@ Error ProjectSettings::save() {
Error ProjectSettings::_save_settings_binary(const String &p_file, const RBMap<String, List<String>> &p_props, const CustomMap &p_custom, const String &p_custom_features) {
Error err;
Ref<FileAccess> file = FileAccess::open(p_file, FileAccess::WRITE, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Couldn't save project.binary at " + p_file + ".");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Couldn't save project.binary at '%s'.", p_file));
uint8_t hdr[4] = { 'E', 'C', 'F', 'G' };
file->store_buffer(hdr, 4);
@@ -948,7 +948,7 @@ Error ProjectSettings::_save_settings_text(const String &p_file, const RBMap<Str
Error err;
Ref<FileAccess> file = FileAccess::open(p_file, FileAccess::WRITE, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Couldn't save project.godot - " + p_file + ".");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Couldn't save project.godot - %s.", p_file));
file->store_line("; Engine configuration file.");
file->store_line("; It's best edited using the editor UI and not directly,");
@@ -1121,7 +1121,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust
} else if (p_path.ends_with(".binary")) {
return _save_settings_binary(p_path, save_props, p_custom, save_features);
} else {
- ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, "Unknown config file format: " + p_path);
+ ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, vformat("Unknown config file format: '%s'.", p_path));
}
}
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 16467c9615..172ca71370 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -77,7 +77,7 @@ Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hi
Error err = OK;
Ref<Resource> ret = ::ResourceLoader::load(p_path, p_type_hint, ResourceFormatLoader::CacheMode(p_cache_mode), &err);
- ERR_FAIL_COND_V_MSG(err != OK, ret, "Error loading resource: '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, ret, vformat("Error loading resource: '%s'.", p_path));
return ret;
}
@@ -1317,7 +1317,7 @@ void Thread::_start_func(void *ud) {
}
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_MSG("Could not call function '" + func_name + "' to start thread " + t->get_id() + ": " + Variant::get_callable_error_text(t->target_callable, nullptr, 0, ce) + ".");
+ ERR_FAIL_MSG(vformat("Could not call function '%s' to start thread %d: %s.", func_name, t->get_id(), Variant::get_callable_error_text(t->target_callable, nullptr, 0, ce)));
}
}
@@ -1542,7 +1542,7 @@ TypedArray<Dictionary> ClassDB::class_get_method_list(const StringName &p_class,
return ret;
}
-Variant ClassDB::class_call_static_method(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) {
+Variant ClassDB::class_call_static(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) {
if (p_argcount < 2) {
r_call_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
return Variant::NIL;
@@ -1683,7 +1683,7 @@ void ClassDB::_bind_methods() {
::ClassDB::bind_method(D_METHOD("class_get_method_list", "class", "no_inheritance"), &ClassDB::class_get_method_list, DEFVAL(false));
- ::ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "class_call_static_method", &ClassDB::class_call_static_method, MethodInfo("class_call_static_method", PropertyInfo(Variant::STRING_NAME, "class"), PropertyInfo(Variant::STRING_NAME, "method")));
+ ::ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "class_call_static", &ClassDB::class_call_static, MethodInfo("class_call_static", PropertyInfo(Variant::STRING_NAME, "class"), PropertyInfo(Variant::STRING_NAME, "method")));
::ClassDB::bind_method(D_METHOD("class_get_integer_constant_list", "class", "no_inheritance"), &ClassDB::class_get_integer_constant_list, DEFVAL(false));
@@ -1828,8 +1828,8 @@ Object *Engine::get_singleton_object(const StringName &p_name) const {
}
void Engine::register_singleton(const StringName &p_name, Object *p_object) {
- ERR_FAIL_COND_MSG(has_singleton(p_name), "Singleton already registered: " + String(p_name));
- ERR_FAIL_COND_MSG(!String(p_name).is_valid_ascii_identifier(), "Singleton name is not a valid identifier: " + p_name);
+ ERR_FAIL_COND_MSG(has_singleton(p_name), vformat("Singleton already registered: '%s'.", String(p_name)));
+ ERR_FAIL_COND_MSG(!String(p_name).is_valid_ascii_identifier(), vformat("Singleton name is not a valid identifier: '%s'.", p_name));
::Engine::Singleton s;
s.class_name = p_name;
s.name = p_name;
@@ -1839,8 +1839,8 @@ void Engine::register_singleton(const StringName &p_name, Object *p_object) {
}
void Engine::unregister_singleton(const StringName &p_name) {
- ERR_FAIL_COND_MSG(!has_singleton(p_name), "Attempt to remove unregistered singleton: " + String(p_name));
- ERR_FAIL_COND_MSG(!::Engine::get_singleton()->is_singleton_user_created(p_name), "Attempt to remove non-user created singleton: " + String(p_name));
+ ERR_FAIL_COND_MSG(!has_singleton(p_name), vformat("Attempt to remove unregistered singleton: '%s'.", String(p_name)));
+ ERR_FAIL_COND_MSG(!::Engine::get_singleton()->is_singleton_user_created(p_name), vformat("Attempt to remove non-user created singleton: '%s'.", String(p_name)));
::Engine::get_singleton()->remove_singleton(p_name);
}
@@ -1987,14 +1987,14 @@ bool EngineDebugger::is_active() {
void EngineDebugger::register_profiler(const StringName &p_name, Ref<EngineProfiler> p_profiler) {
ERR_FAIL_COND(p_profiler.is_null());
ERR_FAIL_COND_MSG(p_profiler->is_bound(), "Profiler already registered.");
- ERR_FAIL_COND_MSG(profilers.has(p_name) || has_profiler(p_name), "Profiler name already in use: " + p_name);
+ ERR_FAIL_COND_MSG(profilers.has(p_name) || has_profiler(p_name), vformat("Profiler name already in use: '%s'.", p_name));
Error err = p_profiler->bind(p_name);
- ERR_FAIL_COND_MSG(err != OK, "Profiler failed to register with error: " + itos(err));
+ ERR_FAIL_COND_MSG(err != OK, vformat("Profiler failed to register with error: %d.", err));
profilers.insert(p_name, p_profiler);
}
void EngineDebugger::unregister_profiler(const StringName &p_name) {
- ERR_FAIL_COND_MSG(!profilers.has(p_name), "Profiler not registered: " + p_name);
+ ERR_FAIL_COND_MSG(!profilers.has(p_name), vformat("Profiler not registered: '%s'.", p_name));
profilers[p_name]->unbind();
profilers.erase(p_name);
}
@@ -2018,7 +2018,7 @@ void EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, c
}
void EngineDebugger::register_message_capture(const StringName &p_name, const Callable &p_callable) {
- ERR_FAIL_COND_MSG(captures.has(p_name) || has_capture(p_name), "Capture already registered: " + p_name);
+ ERR_FAIL_COND_MSG(captures.has(p_name) || has_capture(p_name), vformat("Capture already registered: '%s'.", p_name));
captures.insert(p_name, p_callable);
Callable &c = captures[p_name];
::EngineDebugger::Capture capture(&c, &EngineDebugger::call_capture);
@@ -2026,7 +2026,7 @@ void EngineDebugger::register_message_capture(const StringName &p_name, const Ca
}
void EngineDebugger::unregister_message_capture(const StringName &p_name) {
- ERR_FAIL_COND_MSG(!captures.has(p_name), "Capture not registered: " + p_name);
+ ERR_FAIL_COND_MSG(!captures.has(p_name), vformat("Capture not registered: '%s'.", p_name));
::EngineDebugger::unregister_message_capture(p_name);
captures.erase(p_name);
}
@@ -2060,8 +2060,8 @@ Error EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Arra
Variant retval;
Callable::CallError err;
capture.callp(args, 2, retval, err);
- ERR_FAIL_COND_V_MSG(err.error != Callable::CallError::CALL_OK, FAILED, "Error calling 'capture' to callable: " + Variant::get_callable_error_text(capture, args, 2, err));
- ERR_FAIL_COND_V_MSG(retval.get_type() != Variant::BOOL, FAILED, "Error calling 'capture' to callable: " + String(capture) + ". Return type is not bool.");
+ ERR_FAIL_COND_V_MSG(err.error != Callable::CallError::CALL_OK, FAILED, vformat("Error calling 'capture' to callable: %s.", Variant::get_callable_error_text(capture, args, 2, err)));
+ ERR_FAIL_COND_V_MSG(retval.get_type() != Variant::BOOL, FAILED, vformat("Error calling 'capture' to callable: '%s'. Return type is not bool.", String(capture)));
r_captured = retval;
return OK;
}
diff --git a/core/core_bind.h b/core/core_bind.h
index e44268d539..86828365f9 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -486,7 +486,7 @@ public:
int class_get_method_argument_count(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const;
TypedArray<Dictionary> class_get_method_list(const StringName &p_class, bool p_no_inheritance = false) const;
- Variant class_call_static_method(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error);
+ Variant class_call_static(const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error);
PackedStringArray class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance = false) const;
bool class_has_integer_constant(const StringName &p_class, const StringName &p_name) const;
diff --git a/core/crypto/crypto.cpp b/core/crypto/crypto.cpp
index d41a4233f4..9ba219d73a 100644
--- a/core/crypto/crypto.cpp
+++ b/core/crypto/crypto.cpp
@@ -242,7 +242,7 @@ Error ResourceFormatSaverCrypto::save(const Ref<Resource> &p_resource, const Str
} else {
ERR_FAIL_V(ERR_INVALID_PARAMETER);
}
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save Crypto resource to file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot save Crypto resource to file '%s'.", p_path));
return OK;
}
diff --git a/core/crypto/crypto_core.cpp b/core/crypto/crypto_core.cpp
index 1071a0f0ae..c191066709 100644
--- a/core/crypto/crypto_core.cpp
+++ b/core/crypto/crypto_core.cpp
@@ -72,7 +72,7 @@ int CryptoCore::RandomGenerator::_entropy_poll(void *p_data, unsigned char *r_bu
Error CryptoCore::RandomGenerator::init() {
int ret = mbedtls_ctr_drbg_seed((mbedtls_ctr_drbg_context *)ctx, mbedtls_entropy_func, (mbedtls_entropy_context *)entropy, nullptr, 0);
if (ret) {
- ERR_FAIL_COND_V_MSG(ret, FAILED, " failed\n ! mbedtls_ctr_drbg_seed returned an error" + itos(ret));
+ ERR_FAIL_COND_V_MSG(ret, FAILED, vformat(" failed\n ! mbedtls_ctr_drbg_seed returned an error %d.", ret));
}
return OK;
}
@@ -80,7 +80,7 @@ Error CryptoCore::RandomGenerator::init() {
Error CryptoCore::RandomGenerator::get_random_bytes(uint8_t *r_buffer, size_t p_bytes) {
ERR_FAIL_NULL_V(ctx, ERR_UNCONFIGURED);
int ret = mbedtls_ctr_drbg_random((mbedtls_ctr_drbg_context *)ctx, r_buffer, p_bytes);
- ERR_FAIL_COND_V_MSG(ret, FAILED, " failed\n ! mbedtls_ctr_drbg_seed returned an error" + itos(ret));
+ ERR_FAIL_COND_V_MSG(ret, FAILED, vformat(" failed\n ! mbedtls_ctr_drbg_seed returned an error %d.", ret));
return OK;
}
diff --git a/core/debugger/engine_debugger.cpp b/core/debugger/engine_debugger.cpp
index 3c27691857..0928180591 100644
--- a/core/debugger/engine_debugger.cpp
+++ b/core/debugger/engine_debugger.cpp
@@ -48,12 +48,12 @@ HashMap<String, EngineDebugger::CreatePeerFunc> EngineDebugger::protocols;
void (*EngineDebugger::allow_focus_steal_fn)();
void EngineDebugger::register_profiler(const StringName &p_name, const Profiler &p_func) {
- ERR_FAIL_COND_MSG(profilers.has(p_name), "Profiler already registered: " + p_name);
+ ERR_FAIL_COND_MSG(profilers.has(p_name), vformat("Profiler already registered: '%s'.", p_name));
profilers.insert(p_name, p_func);
}
void EngineDebugger::unregister_profiler(const StringName &p_name) {
- ERR_FAIL_COND_MSG(!profilers.has(p_name), "Profiler not registered: " + p_name);
+ ERR_FAIL_COND_MSG(!profilers.has(p_name), vformat("Profiler not registered: '%s'.", p_name));
Profiler &p = profilers[p_name];
if (p.active && p.toggle) {
p.toggle(p.data, false, Array());
@@ -63,22 +63,22 @@ void EngineDebugger::unregister_profiler(const StringName &p_name) {
}
void EngineDebugger::register_message_capture(const StringName &p_name, Capture p_func) {
- ERR_FAIL_COND_MSG(captures.has(p_name), "Capture already registered: " + p_name);
+ ERR_FAIL_COND_MSG(captures.has(p_name), vformat("Capture already registered: '%s'.", p_name));
captures.insert(p_name, p_func);
}
void EngineDebugger::unregister_message_capture(const StringName &p_name) {
- ERR_FAIL_COND_MSG(!captures.has(p_name), "Capture not registered: " + p_name);
+ ERR_FAIL_COND_MSG(!captures.has(p_name), vformat("Capture not registered: '%s'.", p_name));
captures.erase(p_name);
}
void EngineDebugger::register_uri_handler(const String &p_protocol, CreatePeerFunc p_func) {
- ERR_FAIL_COND_MSG(protocols.has(p_protocol), "Protocol handler already registered: " + p_protocol);
+ ERR_FAIL_COND_MSG(protocols.has(p_protocol), vformat("Protocol handler already registered: '%s'.", p_protocol));
protocols.insert(p_protocol, p_func);
}
void EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts) {
- ERR_FAIL_COND_MSG(!profilers.has(p_name), "Can't change profiler state, no profiler: " + p_name);
+ ERR_FAIL_COND_MSG(!profilers.has(p_name), vformat("Can't change profiler state, no profiler: '%s'.", p_name));
Profiler &p = profilers[p_name];
if (p.toggle) {
p.toggle(p.data, p_enabled, p_opts);
@@ -87,7 +87,7 @@ void EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, c
}
void EngineDebugger::profiler_add_frame_data(const StringName &p_name, const Array &p_data) {
- ERR_FAIL_COND_MSG(!profilers.has(p_name), "Can't add frame data, no profiler: " + p_name);
+ ERR_FAIL_COND_MSG(!profilers.has(p_name), vformat("Can't add frame data, no profiler: '%s'.", p_name));
Profiler &p = profilers[p_name];
if (p.add) {
p.add(p.data, p_data);
@@ -108,7 +108,7 @@ bool EngineDebugger::has_capture(const StringName &p_name) {
Error EngineDebugger::capture_parse(const StringName &p_name, const String &p_msg, const Array &p_args, bool &r_captured) {
r_captured = false;
- ERR_FAIL_COND_V_MSG(!captures.has(p_name), ERR_UNCONFIGURED, "Capture not registered: " + p_name);
+ ERR_FAIL_COND_V_MSG(!captures.has(p_name), ERR_UNCONFIGURED, vformat("Capture not registered: '%s'.", p_name));
const Capture &cap = captures[p_name];
return cap.capture(cap.data, p_msg, p_args, r_captured);
}
@@ -166,7 +166,7 @@ void EngineDebugger::initialize(const String &p_uri, bool p_skip_breakpoints, co
for (int i = 0; i < p_breakpoints.size(); i++) {
const String &bp = p_breakpoints[i];
int sp = bp.rfind(":");
- ERR_CONTINUE_MSG(sp == -1, "Invalid breakpoint: '" + bp + "', expected file:line format.");
+ ERR_CONTINUE_MSG(sp == -1, vformat("Invalid breakpoint: '%s', expected file:line format.", bp));
singleton_script_debugger->insert_breakpoint(bp.substr(sp + 1, bp.length()).to_int(), bp.substr(0, sp));
}
diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp
index aca5f56505..b69a7db989 100644
--- a/core/debugger/remote_debugger.cpp
+++ b/core/debugger/remote_debugger.cpp
@@ -81,7 +81,7 @@ public:
for (int i = 0; i < custom_monitor_names.size(); i++) {
Variant monitor_value = performance->call("get_custom_monitor", custom_monitor_names[i]);
if (!monitor_value.is_num()) {
- ERR_PRINT("Value of custom monitor '" + String(custom_monitor_names[i]) + "' is not a number");
+ ERR_PRINT(vformat("Value of custom monitor '%s' is not a number.", String(custom_monitor_names[i])));
arr[i + max] = Variant();
} else {
arr[i + max] = monitor_value;
@@ -571,7 +571,7 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
bool captured = false;
ERR_CONTINUE(_try_capture(command, data, captured) != OK);
if (!captured) {
- WARN_PRINT("Unknown message received from debugger: " + command);
+ WARN_PRINT(vformat("Unknown message received from debugger: %s.", command));
}
}
} else {
diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp
index 233893dc4f..45a433e125 100644
--- a/core/debugger/remote_debugger_peer.cpp
+++ b/core/debugger/remote_debugger_peer.cpp
@@ -180,7 +180,7 @@ Error RemoteDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_po
}
if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
- ERR_PRINT("Remote Debugger: Unable to connect. Status: " + String::num(tcp_client->get_status()) + ".");
+ ERR_PRINT(vformat("Remote Debugger: Unable to connect. Status: %s.", String::num(tcp_client->get_status())));
return FAILED;
}
connected = true;
diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp
index 2033ff2b43..b70bd23918 100644
--- a/core/extension/extension_api_dump.cpp
+++ b/core/extension/extension_api_dump.cpp
@@ -1366,7 +1366,7 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_
return true; // May just not have this array and its still good. Probably added recently.
}
bool failed = false;
- ERR_FAIL_COND_V_MSG(!p_new_api.has(p_base_array), false, "New API lacks base array: " + p_base_array);
+ ERR_FAIL_COND_V_MSG(!p_new_api.has(p_base_array), false, vformat("New API lacks base array: %s", p_base_array));
Array new_api = p_new_api[p_base_array];
HashMap<String, Dictionary> new_api_assoc;
@@ -1374,6 +1374,9 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_
Dictionary elem = var;
ERR_FAIL_COND_V_MSG(!elem.has(p_name_field), false, vformat("Validate extension JSON: Element of base_array '%s' is missing field '%s'. This is a bug.", base_array, p_name_field));
String name = elem[p_name_field];
+ if (name.is_valid_float()) {
+ name = name.trim_suffix(".0"); // Make "integers" stringified as integers.
+ }
if (p_compare_operators && elem.has("right_type")) {
name += " " + String(elem["right_type"]);
}
@@ -1389,6 +1392,9 @@ static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_
continue;
}
String name = old_elem[p_name_field];
+ if (name.is_valid_float()) {
+ name = name.trim_suffix(".0"); // Make "integers" stringified as integers.
+ }
if (p_compare_operators && old_elem.has("right_type")) {
name += " " + String(old_elem["right_type"]);
}
@@ -1518,7 +1524,7 @@ static bool compare_sub_dict_array(HashSet<String> &r_removed_classes_registered
return true; // May just not have this array and its still good. Probably added recently or optional.
}
bool failed = false;
- ERR_FAIL_COND_V_MSG(!p_new_api.has(p_outer), false, "New API lacks base array: " + p_outer);
+ ERR_FAIL_COND_V_MSG(!p_new_api.has(p_outer), false, vformat("New API lacks base array: %s", p_outer));
Array new_api = p_new_api[p_outer];
HashMap<String, Dictionary> new_api_assoc;
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index 50f83c9d4d..2dc2735fc5 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -357,8 +357,8 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName parent_class_name = *reinterpret_cast<const StringName *>(p_parent_class_name);
- ERR_FAIL_COND_MSG(!String(class_name).is_valid_unicode_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier.");
- ERR_FAIL_COND_MSG(ClassDB::class_exists(class_name), "Attempt to register extension class '" + class_name + "', which appears to be already registered.");
+ ERR_FAIL_COND_MSG(!String(class_name).is_valid_unicode_identifier(), vformat("Attempt to register extension class '%s', which is not a valid class identifier.", class_name));
+ ERR_FAIL_COND_MSG(ClassDB::class_exists(class_name), vformat("Attempt to register extension class '%s', which appears to be already registered.", class_name));
Extension *parent_extension = nullptr;
@@ -372,7 +372,7 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr
//inheriting from engine class
}
} else {
- ERR_FAIL_MSG("Attempt to register an extension class '" + String(class_name) + "' using non-existing parent class '" + String(parent_class_name) + "'.");
+ ERR_FAIL_MSG(vformat("Attempt to register an extension class '%s' using non-existing parent class '%s'.", String(class_name), String(parent_class_name)));
}
#ifdef TOOLS_ENABLED
@@ -465,7 +465,7 @@ void GDExtension::_register_extension_class_method(GDExtensionClassLibraryPtr p_
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName method_name = *reinterpret_cast<const StringName *>(p_method_info->name);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension method '" + String(method_name) + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension method '%s' for unexisting class '%s'.", String(method_name), class_name));
#ifdef TOOLS_ENABLED
Extension *extension = &self->extension_classes[class_name];
@@ -515,7 +515,7 @@ void GDExtension::_register_extension_class_integer_constant(GDExtensionClassLib
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName enum_name = *reinterpret_cast<const StringName *>(p_enum_name);
StringName constant_name = *reinterpret_cast<const StringName *>(p_constant_name);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension constant '" + constant_name + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension constant '%s' for unexisting class '%s'.", constant_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@@ -539,7 +539,7 @@ void GDExtension::_register_extension_class_property_indexed(GDExtensionClassLib
StringName setter = *reinterpret_cast<const StringName *>(p_setter);
StringName getter = *reinterpret_cast<const StringName *>(p_getter);
String property_name = *reinterpret_cast<const StringName *>(p_info->name);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property '" + property_name + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension class property '%s' for unexisting class '%s'.", property_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@@ -560,7 +560,7 @@ void GDExtension::_register_extension_class_property_group(GDExtensionClassLibra
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
String group_name = *reinterpret_cast<const String *>(p_group_name);
String prefix = *reinterpret_cast<const String *>(p_prefix);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property group '" + group_name + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension class property group '%s' for unexisting class '%s'.", group_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@@ -579,7 +579,7 @@ void GDExtension::_register_extension_class_property_subgroup(GDExtensionClassLi
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
String subgroup_name = *reinterpret_cast<const String *>(p_subgroup_name);
String prefix = *reinterpret_cast<const String *>(p_prefix);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property subgroup '" + subgroup_name + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension class property subgroup '%s' for unexisting class '%s'.", subgroup_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@@ -597,7 +597,7 @@ void GDExtension::_register_extension_class_signal(GDExtensionClassLibraryPtr p_
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
StringName signal_name = *reinterpret_cast<const StringName *>(p_signal_name);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class signal '" + signal_name + "' for unexisting class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to register extension class signal '%s' for unexisting class '%s'.", signal_name, class_name));
#ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
@@ -620,7 +620,7 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra
GDExtension *self = reinterpret_cast<GDExtension *>(p_library);
StringName class_name = *reinterpret_cast<const StringName *>(p_class_name);
- ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to unregister unexisting extension class '" + class_name + "'.");
+ ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), vformat("Attempt to unregister unexisting extension class '%s'.", class_name));
Extension *ext = &self->extension_classes[class_name];
#ifdef TOOLS_ENABLED
@@ -628,7 +628,7 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra
self->_clear_extension(ext);
}
#endif
- ERR_FAIL_COND_MSG(ext->gdextension.children.size(), "Attempt to unregister class '" + class_name + "' while other extension classes inherit from it.");
+ ERR_FAIL_COND_MSG(ext->gdextension.children.size(), vformat("Attempt to unregister class '%s' while other extension classes inherit from it.", class_name));
#ifdef TOOLS_ENABLED
ClassDB::unregister_extension_class(class_name, !ext->is_reloading);
@@ -666,13 +666,13 @@ void GDExtension::_get_library_path(GDExtensionClassLibraryPtr p_library, GDExte
HashMap<StringName, GDExtensionInterfaceFunctionPtr> GDExtension::gdextension_interface_functions;
void GDExtension::register_interface_function(const StringName &p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer) {
- ERR_FAIL_COND_MSG(gdextension_interface_functions.has(p_function_name), "Attempt to register interface function '" + p_function_name + "', which appears to be already registered.");
+ ERR_FAIL_COND_MSG(gdextension_interface_functions.has(p_function_name), vformat("Attempt to register interface function '%s', which appears to be already registered.", p_function_name));
gdextension_interface_functions.insert(p_function_name, p_function_pointer);
}
GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(const StringName &p_function_name) {
GDExtensionInterfaceFunctionPtr *function = gdextension_interface_functions.getptr(p_function_name);
- ERR_FAIL_NULL_V_MSG(function, nullptr, "Attempt to get non-existent interface function: " + String(p_function_name) + ".");
+ ERR_FAIL_NULL_V_MSG(function, nullptr, vformat("Attempt to get non-existent interface function: '%s'.", String(p_function_name)));
return *function;
}
@@ -682,8 +682,8 @@ Error GDExtension::open_library(const String &p_path, const Ref<GDExtensionLoade
Error err = loader->open_library(p_path);
- ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + p_path);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + p_path);
+ ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, vformat("GDExtension dynamic library not found: '%s'.", p_path));
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Can't open GDExtension dynamic library: '%s'.", p_path));
err = loader->initialize(&gdextension_get_proc_address, this, &initialization);
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index 9d5f71e5a6..7ae7355219 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -1528,7 +1528,7 @@ static GDExtensionMethodBindPtr gdextension_classdb_get_method_bind(GDExtensionC
#endif
if (!mb && exists) {
- ERR_PRINT("Method '" + classname + "." + methodname + "' has changed and no compatibility fallback has been provided. Please open an issue.");
+ ERR_PRINT(vformat("Method '%s.%s' has changed and no compatibility fallback has been provided. Please open an issue.", classname, methodname));
return nullptr;
}
ERR_FAIL_NULL_V(mb, nullptr);
diff --git a/core/extension/gdextension_library_loader.cpp b/core/extension/gdextension_library_loader.cpp
index 48eec44a8c..3ff4f24ed7 100644
--- a/core/extension/gdextension_library_loader.cpp
+++ b/core/extension/gdextension_library_loader.cpp
@@ -221,7 +221,7 @@ Error GDExtensionLibraryLoader::initialize(GDExtensionInterfaceGetProcAddress p_
Error err = OS::get_singleton()->get_dynamic_library_symbol_handle(library, entry_symbol, entry_funcptr, false);
if (err != OK) {
- ERR_PRINT("GDExtension entry point '" + entry_symbol + "' not found in library " + library_path);
+ ERR_PRINT(vformat("GDExtension entry point '%s' not found in library %s.", entry_symbol, library_path));
return err;
}
@@ -232,7 +232,7 @@ Error GDExtensionLibraryLoader::initialize(GDExtensionInterfaceGetProcAddress p_
if (ret) {
return OK;
} else {
- ERR_PRINT("GDExtension initialization function '" + entry_symbol + "' returned an error.");
+ ERR_PRINT(vformat("GDExtension initialization function '%s' returned an error.", entry_symbol));
return FAILED;
}
}
@@ -274,12 +274,12 @@ Error GDExtensionLibraryLoader::parse_gdextension_file(const String &p_path) {
Error err = config->load(p_path);
if (err != OK) {
- ERR_PRINT("Error loading GDExtension configuration file: " + p_path);
+ ERR_PRINT(vformat("Error loading GDExtension configuration file: '%s'.", p_path));
return err;
}
if (!config->has_section_key("configuration", "entry_symbol")) {
- ERR_PRINT("GDExtension configuration file must contain a \"configuration/entry_symbol\" key: " + p_path);
+ ERR_PRINT(vformat("GDExtension configuration file must contain a \"configuration/entry_symbol\" key: '%s'.", p_path));
return ERR_INVALID_DATA;
}
@@ -298,7 +298,7 @@ Error GDExtensionLibraryLoader::parse_gdextension_file(const String &p_path) {
}
}
} else {
- ERR_PRINT("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: " + p_path);
+ ERR_PRINT(vformat("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: '%s'.", p_path));
return ERR_INVALID_DATA;
}
diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp
index 98596bda27..66203cbbad 100644
--- a/core/extension/gdextension_manager.cpp
+++ b/core/extension/gdextension_manager.cpp
@@ -260,7 +260,7 @@ void GDExtensionManager::load_extensions() {
String s = f->get_line().strip_edges();
if (!s.is_empty()) {
LoadStatus err = load_extension(s);
- ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, "Error loading extension: " + s);
+ ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, vformat("Error loading extension: '%s'.", s));
}
}
diff --git a/core/input/input.cpp b/core/input/input.cpp
index 695d3f131a..0b82292c7c 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -89,11 +89,50 @@ Input *Input::get_singleton() {
void Input::set_mouse_mode(MouseMode p_mode) {
ERR_FAIL_INDEX((int)p_mode, 5);
+
+ if (p_mode == mouse_mode) {
+ return;
+ }
+
+ // Allow to be set even if overridden, to see if the platform allows the mode.
set_mouse_mode_func(p_mode);
+ mouse_mode = get_mouse_mode_func();
+
+ if (mouse_mode_override_enabled) {
+ set_mouse_mode_func(mouse_mode_override);
+ }
}
Input::MouseMode Input::get_mouse_mode() const {
- return get_mouse_mode_func();
+ return mouse_mode;
+}
+
+void Input::set_mouse_mode_override_enabled(bool p_enabled) {
+ if (p_enabled == mouse_mode_override_enabled) {
+ return;
+ }
+
+ mouse_mode_override_enabled = p_enabled;
+
+ if (p_enabled) {
+ set_mouse_mode_func(mouse_mode_override);
+ mouse_mode_override = get_mouse_mode_func();
+ } else {
+ set_mouse_mode_func(mouse_mode);
+ }
+}
+
+void Input::set_mouse_mode_override(MouseMode p_mode) {
+ ERR_FAIL_INDEX((int)p_mode, 5);
+
+ if (p_mode == mouse_mode_override) {
+ return;
+ }
+
+ if (mouse_mode_override_enabled) {
+ set_mouse_mode_func(p_mode);
+ mouse_mode_override = get_mouse_mode_func();
+ }
}
void Input::_bind_methods() {
@@ -254,6 +293,10 @@ Input::VelocityTrack::VelocityTrack() {
bool Input::is_anything_pressed() const {
_THREAD_SAFE_METHOD_
+ if (disable_input) {
+ return false;
+ }
+
if (!keys_pressed.is_empty() || !joy_buttons_pressed.is_empty() || !mouse_button_mask.is_empty()) {
return true;
}
@@ -269,21 +312,41 @@ bool Input::is_anything_pressed() const {
bool Input::is_key_pressed(Key p_keycode) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return false;
+ }
+
return keys_pressed.has(p_keycode);
}
bool Input::is_physical_key_pressed(Key p_keycode) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return false;
+ }
+
return physical_keys_pressed.has(p_keycode);
}
bool Input::is_key_label_pressed(Key p_keycode) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return false;
+ }
+
return key_label_pressed.has(p_keycode);
}
bool Input::is_mouse_button_pressed(MouseButton p_button) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return false;
+ }
+
return mouse_button_mask.has_flag(mouse_button_to_mask(p_button));
}
@@ -297,11 +360,21 @@ static JoyButton _combine_device(JoyButton p_value, int p_device) {
bool Input::is_joy_button_pressed(int p_device, JoyButton p_button) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return false;
+ }
+
return joy_buttons_pressed.has(_combine_device(p_button, p_device));
}
bool Input::is_action_pressed(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
+
+ if (disable_input) {
+ return false;
+ }
+
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
@@ -312,6 +385,11 @@ bool Input::is_action_pressed(const StringName &p_action, bool p_exact) const {
bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
+
+ if (disable_input) {
+ return false;
+ }
+
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
@@ -333,6 +411,11 @@ bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) con
bool Input::is_action_just_released(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
+
+ if (disable_input) {
+ return false;
+ }
+
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
@@ -354,6 +437,11 @@ bool Input::is_action_just_released(const StringName &p_action, bool p_exact) co
float Input::get_action_strength(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action));
+
+ if (disable_input) {
+ return 0.0f;
+ }
+
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return 0.0f;
@@ -368,6 +456,11 @@ float Input::get_action_strength(const StringName &p_action, bool p_exact) const
float Input::get_action_raw_strength(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action));
+
+ if (disable_input) {
+ return 0.0f;
+ }
+
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return 0.0f;
@@ -412,6 +505,11 @@ Vector2 Input::get_vector(const StringName &p_negative_x, const StringName &p_po
float Input::get_joy_axis(int p_device, JoyAxis p_axis) const {
_THREAD_SAFE_METHOD_
+
+ if (disable_input) {
+ return 0;
+ }
+
JoyAxis c = _combine_device(p_axis, p_device);
if (_joy_axis.has(c)) {
return _joy_axis[c];
@@ -1666,6 +1764,14 @@ int Input::get_unused_joy_id() {
return -1;
}
+void Input::set_disable_input(bool p_disable) {
+ disable_input = p_disable;
+}
+
+bool Input::is_input_disabled() const {
+ return disable_input;
+}
+
Input::Input() {
singleton = this;
diff --git a/core/input/input.h b/core/input/input.h
index 32db2c0f2f..a4c8bd1d6a 100644
--- a/core/input/input.h
+++ b/core/input/input.h
@@ -105,6 +105,11 @@ private:
Vector2 mouse_pos;
int64_t mouse_window = 0;
bool legacy_just_pressed_behavior = false;
+ bool disable_input = false;
+
+ MouseMode mouse_mode = MOUSE_MODE_VISIBLE;
+ bool mouse_mode_override_enabled = false;
+ MouseMode mouse_mode_override = MOUSE_MODE_VISIBLE;
struct ActionState {
uint64_t pressed_physics_frame = UINT64_MAX;
@@ -281,6 +286,8 @@ protected:
public:
void set_mouse_mode(MouseMode p_mode);
MouseMode get_mouse_mode() const;
+ void set_mouse_mode_override_enabled(bool p_enabled);
+ void set_mouse_mode_override(MouseMode p_mode);
#ifdef TOOLS_ENABLED
void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
@@ -382,6 +389,9 @@ public:
void set_event_dispatch_function(EventDispatchFunc p_function);
+ void set_disable_input(bool p_disable);
+ bool is_input_disabled() const;
+
Input();
~Input();
};
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp
index 9c10b143dd..553de4af9c 100644
--- a/core/input/input_map.cpp
+++ b/core/input/input_map.cpp
@@ -116,7 +116,7 @@ void InputMap::get_argument_options(const StringName &p_function, int p_idx, Lis
#endif
void InputMap::add_action(const StringName &p_action, float p_deadzone) {
- ERR_FAIL_COND_MSG(input_map.has(p_action), "InputMap already has action \"" + String(p_action) + "\".");
+ ERR_FAIL_COND_MSG(input_map.has(p_action), vformat("InputMap already has action \"%s\".", String(p_action)));
input_map[p_action] = Action();
static int last_id = 1;
input_map[p_action].id = last_id;
diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp
index 7d76156309..18df616332 100644
--- a/core/io/dir_access.cpp
+++ b/core/io/dir_access.cpp
@@ -179,7 +179,7 @@ Error DirAccess::make_dir_recursive(const String &p_dir) {
curpath = curpath.path_join(subdirs[i]);
Error err = make_dir(curpath);
if (err != OK && err != ERR_ALREADY_EXISTS) {
- ERR_FAIL_V_MSG(err, "Could not create directory: " + curpath);
+ ERR_FAIL_V_MSG(err, vformat("Could not create directory: '%s'.", curpath));
}
}
@@ -241,7 +241,7 @@ Ref<DirAccess> DirAccess::create_for_path(const String &p_path) {
Ref<DirAccess> DirAccess::open(const String &p_path, Error *r_error) {
Ref<DirAccess> da = create_for_path(p_path);
- ERR_FAIL_COND_V_MSG(da.is_null(), nullptr, "Cannot create DirAccess for path '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(da.is_null(), nullptr, vformat("Cannot create DirAccess for path '%s'.", p_path));
Error err = da->change_dir(p_path);
if (r_error) {
*r_error = err;
@@ -347,10 +347,10 @@ Error DirAccess::copy(const String &p_from, const String &p_to, int p_chmod_flag
Error err;
{
Ref<FileAccess> fsrc = FileAccess::open(p_from, FileAccess::READ, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to open " + p_from);
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Failed to open '%s'.", p_from));
Ref<FileAccess> fdst = FileAccess::open(p_to, FileAccess::WRITE, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to open " + p_to);
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Failed to open '%s'.", p_to));
const size_t copy_buffer_limit = 65536; // 64 KB
@@ -446,11 +446,11 @@ Error DirAccess::_copy_dir(Ref<DirAccess> &p_target_da, const String &p_to, int
String target_dir = p_to + rel_path;
if (!p_target_da->dir_exists(target_dir)) {
Error err = p_target_da->make_dir(target_dir);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + target_dir + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot create directory '%s'.", target_dir));
}
Error err = change_dir(rel_path);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot change current directory to '" + rel_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot change current directory to '%s'.", rel_path));
err = _copy_dir(p_target_da, p_to + rel_path + "/", p_chmod_flags, p_copy_links);
if (err) {
@@ -468,11 +468,11 @@ Error DirAccess::copy_dir(const String &p_from, String p_to, int p_chmod_flags,
ERR_FAIL_COND_V_MSG(!dir_exists(p_from), ERR_FILE_NOT_FOUND, "Source directory doesn't exist.");
Ref<DirAccess> target_da = DirAccess::create_for_path(p_to);
- ERR_FAIL_COND_V_MSG(target_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_to + "'.");
+ ERR_FAIL_COND_V_MSG(target_da.is_null(), ERR_CANT_CREATE, vformat("Cannot create DirAccess for path '%s'.", p_to));
if (!target_da->dir_exists(p_to)) {
Error err = target_da->make_dir_recursive(p_to);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create directory '" + p_to + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot create directory '%s'.", p_to));
}
if (!p_to.ends_with("/")) {
diff --git a/core/io/dir_access.h b/core/io/dir_access.h
index 2567764e55..670c512ea9 100644
--- a/core/io/dir_access.h
+++ b/core/io/dir_access.h
@@ -118,10 +118,10 @@ public:
Ref<DirAccess> da = create(ACCESS_FILESYSTEM);
if (da->file_exists(p_path)) {
if (da->remove(p_path) != OK) {
- ERR_FAIL_MSG("Cannot remove file or directory: " + p_path);
+ ERR_FAIL_MSG(vformat("Cannot remove file or directory: '%s'.", p_path));
}
} else {
- ERR_FAIL_MSG("Cannot remove non-existent file or directory: " + p_path);
+ ERR_FAIL_MSG(vformat("Cannot remove non-existent file or directory: '%s'.", p_path));
}
}
diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp
index 69ad8bbeda..e10462adcd 100644
--- a/core/io/file_access.cpp
+++ b/core/io/file_access.cpp
@@ -461,7 +461,7 @@ Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const {
}
Error err = data.resize(p_length);
- ERR_FAIL_COND_V_MSG(err != OK, data, "Can't resize data to " + itos(p_length) + " elements.");
+ ERR_FAIL_COND_V_MSG(err != OK, data, vformat("Can't resize data to %d elements.", p_length));
uint8_t *w = data.ptrw();
int64_t len = get_buffer(w, p_length);
@@ -542,7 +542,7 @@ uint64_t FileAccess::get_modified_time(const String &p_file) {
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, vformat("Cannot create FileAccess for path '%s'.", p_file));
uint64_t mt = fa->_get_modified_time(p_file);
return mt;
@@ -554,7 +554,7 @@ BitField<FileAccess::UnixPermissionFlags> FileAccess::get_unix_permissions(const
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, vformat("Cannot create FileAccess for path '%s'.", p_file));
return fa->_get_unix_permissions(p_file);
}
@@ -565,7 +565,7 @@ Error FileAccess::set_unix_permissions(const String &p_file, BitField<FileAccess
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, vformat("Cannot create FileAccess for path '%s'.", p_file));
Error err = fa->_set_unix_permissions(p_file, p_permissions);
return err;
@@ -577,7 +577,7 @@ bool FileAccess::get_hidden_attribute(const String &p_file) {
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), false, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("Cannot create FileAccess for path '%s'.", p_file));
return fa->_get_hidden_attribute(p_file);
}
@@ -588,7 +588,7 @@ Error FileAccess::set_hidden_attribute(const String &p_file, bool p_hidden) {
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, vformat("Cannot create FileAccess for path '%s'.", p_file));
Error err = fa->_set_hidden_attribute(p_file, p_hidden);
return err;
@@ -600,7 +600,7 @@ bool FileAccess::get_read_only_attribute(const String &p_file) {
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), false, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("Cannot create FileAccess for path '%s'.", p_file));
return fa->_get_read_only_attribute(p_file);
}
@@ -611,7 +611,7 @@ Error FileAccess::set_read_only_attribute(const String &p_file, bool p_ro) {
}
Ref<FileAccess> fa = create_for_path(p_file);
- ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create FileAccess for path '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, vformat("Cannot create FileAccess for path '%s'.", p_file));
Error err = fa->_set_read_only_attribute(p_file, p_ro);
return err;
@@ -699,7 +699,7 @@ Vector<uint8_t> FileAccess::get_file_as_bytes(const String &p_path, Error *r_err
if (r_error) { // if error requested, do not throw error
return Vector<uint8_t>();
}
- ERR_FAIL_V_MSG(Vector<uint8_t>(), "Can't open file from path '" + String(p_path) + "'.");
+ ERR_FAIL_V_MSG(Vector<uint8_t>(), vformat("Can't open file from path '%s'.", String(p_path)));
}
Vector<uint8_t> data;
data.resize(f->get_length());
@@ -717,7 +717,7 @@ String FileAccess::get_file_as_string(const String &p_path, Error *r_error) {
if (r_error) {
return String();
}
- ERR_FAIL_V_MSG(String(), "Can't get file as string from path '" + String(p_path) + "'.");
+ ERR_FAIL_V_MSG(String(), vformat("Can't get file as string from path '%s'.", String(p_path)));
}
String ret;
diff --git a/core/io/file_access_compressed.cpp b/core/io/file_access_compressed.cpp
index 05335df9e9..84137251ef 100644
--- a/core/io/file_access_compressed.cpp
+++ b/core/io/file_access_compressed.cpp
@@ -60,7 +60,7 @@ Error FileAccessCompressed::open_after_magic(Ref<FileAccess> p_base) {
block_size = f->get_32();
if (block_size == 0) {
f.unref();
- ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Can't open compressed file '" + p_base->get_path() + "' with block size 0, it is corrupted.");
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, vformat("Can't open compressed file '%s' with block size 0, it is corrupted.", p_base->get_path()));
}
read_total = f->get_32();
uint32_t bc = (read_total / block_size) + 1;
diff --git a/core/io/file_access_encrypted.cpp b/core/io/file_access_encrypted.cpp
index 6c406afff0..a84aa4c800 100644
--- a/core/io/file_access_encrypted.cpp
+++ b/core/io/file_access_encrypted.cpp
@@ -39,7 +39,7 @@
#include <stdio.h>
Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic) {
- ERR_FAIL_COND_V_MSG(file.is_valid(), ERR_ALREADY_IN_USE, "Can't open file while another file from path '" + file->get_path_absolute() + "' is open.");
+ ERR_FAIL_COND_V_MSG(file.is_valid(), ERR_ALREADY_IN_USE, vformat("Can't open file while another file from path '%s' is open.", file->get_path_absolute()));
ERR_FAIL_COND_V(p_key.size() != 32, ERR_INVALID_PARAMETER);
pos = 0;
diff --git a/core/io/file_access_memory.cpp b/core/io/file_access_memory.cpp
index 199a51e86d..2ead49ba81 100644
--- a/core/io/file_access_memory.cpp
+++ b/core/io/file_access_memory.cpp
@@ -87,7 +87,7 @@ Error FileAccessMemory::open_internal(const String &p_path, int p_mode_flags) {
//name = DirAccess::normalize_path(name);
HashMap<String, Vector<uint8_t>>::Iterator E = files->find(name);
- ERR_FAIL_COND_V_MSG(!E, ERR_FILE_NOT_FOUND, "Can't find file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(!E, ERR_FILE_NOT_FOUND, vformat("Can't find file '%s'.", p_path));
data = E->value.ptrw();
length = E->value.size();
diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp
index 6f2685163e..7bc96c4644 100644
--- a/core/io/file_access_pack.cpp
+++ b/core/io/file_access_pack.cpp
@@ -225,8 +225,8 @@ bool PackedSourcePCK::try_open_pack(const String &p_path, bool p_replace_files,
uint32_t ver_minor = f->get_32();
f->get_32(); // patch number, not used for validation.
- ERR_FAIL_COND_V_MSG(version != PACK_FORMAT_VERSION, false, "Pack version unsupported: " + itos(version) + ".");
- ERR_FAIL_COND_V_MSG(ver_major > VERSION_MAJOR || (ver_major == VERSION_MAJOR && ver_minor > VERSION_MINOR), false, "Pack created with a newer version of the engine: " + itos(ver_major) + "." + itos(ver_minor) + ".");
+ ERR_FAIL_COND_V_MSG(version != PACK_FORMAT_VERSION, false, vformat("Pack version unsupported: %d.", version));
+ ERR_FAIL_COND_V_MSG(ver_major > VERSION_MAJOR || (ver_major == VERSION_MAJOR && ver_minor > VERSION_MINOR), false, vformat("Pack created with a newer version of the engine: %d.%d.", ver_major, ver_minor));
uint32_t pack_flags = f->get_32();
uint64_t file_base = f->get_64();
@@ -388,7 +388,7 @@ void FileAccessPack::close() {
FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFile &p_file) :
pf(p_file),
f(FileAccess::open(pf.pack, FileAccess::READ)) {
- ERR_FAIL_COND_MSG(f.is_null(), "Can't open pack-referenced file '" + String(pf.pack) + "'.");
+ ERR_FAIL_COND_MSG(f.is_null(), vformat("Can't open pack-referenced file '%s'.", String(pf.pack)));
f->seek(pf.offset);
off = pf.offset;
@@ -396,7 +396,7 @@ FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFil
if (pf.encrypted) {
Ref<FileAccessEncrypted> fae;
fae.instantiate();
- ERR_FAIL_COND_MSG(fae.is_null(), "Can't open encrypted pack-referenced file '" + String(pf.pack) + "'.");
+ ERR_FAIL_COND_MSG(fae.is_null(), vformat("Can't open encrypted pack-referenced file '%s'.", String(pf.pack)));
Vector<uint8_t> key;
key.resize(32);
@@ -405,7 +405,7 @@ FileAccessPack::FileAccessPack(const String &p_path, const PackedData::PackedFil
}
Error err = fae->open_and_parse(f, key, FileAccessEncrypted::MODE_READ, false);
- ERR_FAIL_COND_MSG(err, "Can't open encrypted pack-referenced file '" + String(pf.pack) + "'.");
+ ERR_FAIL_COND_MSG(err, vformat("Can't open encrypted pack-referenced file '%s'.", String(pf.pack)));
f = fae;
off = 0;
}
diff --git a/core/io/file_access_zip.cpp b/core/io/file_access_zip.cpp
index 38f86bd04d..71ce18145a 100644
--- a/core/io/file_access_zip.cpp
+++ b/core/io/file_access_zip.cpp
@@ -118,7 +118,7 @@ void ZipArchive::close_handle(unzFile p_file) const {
}
unzFile ZipArchive::get_file_handle(const String &p_file) const {
- ERR_FAIL_COND_V_MSG(!file_exists(p_file), nullptr, "File '" + p_file + " doesn't exist.");
+ ERR_FAIL_COND_V_MSG(!file_exists(p_file), nullptr, vformat("File '%s' doesn't exist.", p_file));
File file = files[p_file];
zlib_filefunc_def io;
@@ -138,7 +138,7 @@ unzFile ZipArchive::get_file_handle(const String &p_file) const {
io.free_mem = godot_free;
unzFile pkg = unzOpen2(packages[file.package].filename.utf8().get_data(), &io);
- ERR_FAIL_NULL_V_MSG(pkg, nullptr, "Cannot open file '" + packages[file.package].filename + "'.");
+ ERR_FAIL_NULL_V_MSG(pkg, nullptr, vformat("Cannot open file '%s'.", packages[file.package].filename));
int unz_err = unzGoToFilePos(pkg, &file.file_pos);
if (unz_err != UNZ_OK || unzOpenCurrentFile(pkg) != UNZ_OK) {
unzClose(pkg);
diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp
index 323990ef7e..9d2adb2fcf 100644
--- a/core/io/http_client.cpp
+++ b/core/io/http_client.cpp
@@ -102,9 +102,9 @@ String HTTPClient::query_string_from_dict(const Dictionary &p_dict) {
Error HTTPClient::verify_headers(const Vector<String> &p_headers) {
for (int i = 0; i < p_headers.size(); i++) {
String sanitized = p_headers[i].strip_edges();
- ERR_FAIL_COND_V_MSG(sanitized.is_empty(), ERR_INVALID_PARAMETER, "Invalid HTTP header at index " + itos(i) + ": empty.");
+ ERR_FAIL_COND_V_MSG(sanitized.is_empty(), ERR_INVALID_PARAMETER, vformat("Invalid HTTP header at index %d: empty.", i));
ERR_FAIL_COND_V_MSG(sanitized.find(":") < 1, ERR_INVALID_PARAMETER,
- "Invalid HTTP header at index " + itos(i) + ": String must contain header-value pair, delimited by ':', but was: " + p_headers[i]);
+ vformat("Invalid HTTP header at index %d: String must contain header-value pair, delimited by ':', but was: '%s'.", i, p_headers[i]));
}
return OK;
diff --git a/core/io/image.cpp b/core/io/image.cpp
index 40f1499f77..613e740dd6 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -537,7 +537,7 @@ static bool _are_formats_compatible(Image::Format p_format0, Image::Format p_for
}
void Image::convert(Format p_new_format) {
- ERR_FAIL_INDEX_MSG(p_new_format, FORMAT_MAX, "The Image format specified (" + itos(p_new_format) + ") is out of range. See Image's Format enum.");
+ ERR_FAIL_INDEX_MSG(p_new_format, FORMAT_MAX, vformat("The Image format specified (%d) is out of range. See Image's Format enum.", p_new_format));
if (data.size() == 0) {
return;
}
@@ -1134,9 +1134,9 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) {
ERR_FAIL_COND_MSG(p_width <= 0, "Image width must be greater than 0.");
ERR_FAIL_COND_MSG(p_height <= 0, "Image height must be greater than 0.");
- ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, "Image width cannot be greater than " + itos(MAX_WIDTH) + ".");
- ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, "Image height cannot be greater than " + itos(MAX_HEIGHT) + ".");
- ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS, "Too many pixels for image, maximum is " + itos(MAX_PIXELS));
+ ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, vformat("Image width cannot be greater than %d pixels.", MAX_WIDTH));
+ ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, vformat("Image height cannot be greater than %d pixels.", MAX_HEIGHT));
+ ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS, vformat("Too many pixels for image, maximum is %d pixels.", MAX_PIXELS));
if (p_width == width && p_height == height) {
return;
@@ -1437,8 +1437,8 @@ void Image::crop_from_point(int p_x, int p_y, int p_width, int p_height) {
ERR_FAIL_COND_MSG(p_y < 0, "Start y position cannot be smaller than 0.");
ERR_FAIL_COND_MSG(p_width <= 0, "Width of image must be greater than 0.");
ERR_FAIL_COND_MSG(p_height <= 0, "Height of image must be greater than 0.");
- ERR_FAIL_COND_MSG(p_x + p_width > MAX_WIDTH, "End x position cannot be greater than " + itos(MAX_WIDTH) + ".");
- ERR_FAIL_COND_MSG(p_y + p_height > MAX_HEIGHT, "End y position cannot be greater than " + itos(MAX_HEIGHT) + ".");
+ ERR_FAIL_COND_MSG(p_x + p_width > MAX_WIDTH, vformat("End x position cannot be greater than %d.", MAX_WIDTH));
+ ERR_FAIL_COND_MSG(p_y + p_height > MAX_HEIGHT, vformat("End y position cannot be greater than %d.", MAX_HEIGHT));
/* to save memory, cropping should be done in-place, however, since this function
will most likely either not be used much, or in critical areas, for now it won't, because
@@ -1486,8 +1486,8 @@ void Image::crop(int p_width, int p_height) {
void Image::rotate_90(ClockDirection p_direction) {
ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot rotate in compressed or custom image formats.");
- ERR_FAIL_COND_MSG(width <= 0, "The Image width specified (" + itos(width) + " pixels) must be greater than 0 pixels.");
- ERR_FAIL_COND_MSG(height <= 0, "The Image height specified (" + itos(height) + " pixels) must be greater than 0 pixels.");
+ ERR_FAIL_COND_MSG(width <= 0, vformat("The Image width specified (%d pixels) must be greater than 0 pixels.", width));
+ ERR_FAIL_COND_MSG(height <= 0, vformat("The Image height specified (%d pixels) must be greater than 0 pixels.", height));
bool used_mipmaps = has_mipmaps();
if (used_mipmaps) {
@@ -1604,8 +1604,8 @@ void Image::rotate_90(ClockDirection p_direction) {
void Image::rotate_180() {
ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot rotate in compressed or custom image formats.");
- ERR_FAIL_COND_MSG(width <= 0, "The Image width specified (" + itos(width) + " pixels) must be greater than 0 pixels.");
- ERR_FAIL_COND_MSG(height <= 0, "The Image height specified (" + itos(height) + " pixels) must be greater than 0 pixels.");
+ ERR_FAIL_COND_MSG(width <= 0, vformat("The Image width specified (%d pixels) must be greater than 0 pixels.", width));
+ ERR_FAIL_COND_MSG(height <= 0, vformat("The Image height specified (%d pixels) must be greater than 0 pixels.", height));
bool used_mipmaps = has_mipmaps();
if (used_mipmaps) {
@@ -2251,15 +2251,15 @@ void Image::set_data(int p_width, int p_height, bool p_use_mipmaps, Format p_for
}
void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format) {
- ERR_FAIL_COND_MSG(p_width <= 0, "The Image width specified (" + itos(p_width) + " pixels) must be greater than 0 pixels.");
- ERR_FAIL_COND_MSG(p_height <= 0, "The Image height specified (" + itos(p_height) + " pixels) must be greater than 0 pixels.");
+ ERR_FAIL_COND_MSG(p_width <= 0, vformat("The Image width specified (%d pixels) must be greater than 0 pixels.", p_width));
+ ERR_FAIL_COND_MSG(p_height <= 0, vformat("The Image height specified (%d pixels) must be greater than 0 pixels.", p_height));
ERR_FAIL_COND_MSG(p_width > MAX_WIDTH,
- "The Image width specified (" + itos(p_width) + " pixels) cannot be greater than " + itos(MAX_WIDTH) + "pixels.");
+ vformat("The Image width specified (%d pixels) cannot be greater than %d pixels.", p_width, MAX_WIDTH));
ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT,
- "The Image height specified (" + itos(p_height) + " pixels) cannot be greater than " + itos(MAX_HEIGHT) + "pixels.");
+ vformat("The Image height specified (%d pixels) cannot be greater than %d pixels.", p_height, MAX_HEIGHT));
ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS,
- "Too many pixels for Image. Maximum is " + itos(MAX_WIDTH) + "x" + itos(MAX_HEIGHT) + " = " + itos(MAX_PIXELS) + "pixels.");
- ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum.");
+ vformat("Too many pixels for Image. Maximum is %dx%d = %d pixels.", MAX_WIDTH, MAX_HEIGHT, MAX_PIXELS));
+ ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, vformat("The Image format specified (%d) is out of range. See Image's Format enum.", p_format));
int mm = 0;
int64_t size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0);
@@ -2277,15 +2277,15 @@ void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Forma
}
void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Format p_format, const Vector<uint8_t> &p_data) {
- ERR_FAIL_COND_MSG(p_width <= 0, "The Image width specified (" + itos(p_width) + " pixels) must be greater than 0 pixels.");
- ERR_FAIL_COND_MSG(p_height <= 0, "The Image height specified (" + itos(p_height) + " pixels) must be greater than 0 pixels.");
+ ERR_FAIL_COND_MSG(p_width <= 0, vformat("The Image width specified (%d pixels) must be greater than 0 pixels.", p_width));
+ ERR_FAIL_COND_MSG(p_height <= 0, vformat("The Image height specified (%d pixels) must be greater than 0 pixels.", p_height));
ERR_FAIL_COND_MSG(p_width > MAX_WIDTH,
- "The Image width specified (" + itos(p_width) + " pixels) cannot be greater than " + itos(MAX_WIDTH) + " pixels.");
+ vformat("The Image width specified (%d pixels) cannot be greater than %d pixels.", p_width, MAX_WIDTH));
ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT,
- "The Image height specified (" + itos(p_height) + " pixels) cannot be greater than " + itos(MAX_HEIGHT) + " pixels.");
+ vformat("The Image height specified (%d pixels) cannot be greater than %d pixels.", p_height, MAX_HEIGHT));
ERR_FAIL_COND_MSG(p_width * p_height > MAX_PIXELS,
- "Too many pixels for Image. Maximum is " + itos(MAX_WIDTH) + "x" + itos(MAX_HEIGHT) + " = " + itos(MAX_PIXELS) + "pixels .");
- ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum.");
+ vformat("Too many pixels for Image. Maximum is %dx%d = %d pixels.", MAX_WIDTH, MAX_HEIGHT, MAX_PIXELS));
+ ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, vformat("The Image format specified (%d) is out of range. See Image's Format enum.", p_format));
int mm;
int64_t size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0);
@@ -2579,7 +2579,7 @@ Image::AlphaMode Image::detect_alpha() const {
Error Image::load(const String &p_path) {
#ifdef DEBUG_ENABLED
if (p_path.begins_with("res://") && ResourceLoader::exists(p_path)) {
- WARN_PRINT("Loaded resource as image file, this will not work on export: '" + p_path + "'. Instead, import the image file as an Image resource and load it normally as a resource.");
+ WARN_PRINT(vformat("Loaded resource as image file, this will not work on export: '%s'. Instead, import the image file as an Image resource and load it normally as a resource.", p_path));
}
#endif
return ImageLoader::load_image(p_path, this);
@@ -2588,7 +2588,7 @@ Error Image::load(const String &p_path) {
Ref<Image> Image::load_from_file(const String &p_path) {
#ifdef DEBUG_ENABLED
if (p_path.begins_with("res://") && ResourceLoader::exists(p_path)) {
- WARN_PRINT("Loaded resource as image file, this will not work on export: '" + p_path + "'. Instead, import the image file as an Image resource and load it normally as a resource.");
+ WARN_PRINT(vformat("Loaded resource as image file, this will not work on export: '%s'. Instead, import the image file as an Image resource and load it normally as a resource.", p_path));
}
#endif
Ref<Image> image;
@@ -2651,7 +2651,7 @@ Error Image::save_webp(const String &p_path, const bool p_lossy, const float p_q
if (save_webp_func == nullptr) {
return ERR_UNAVAILABLE;
}
- ERR_FAIL_COND_V_MSG(p_lossy && !(0.0f <= p_quality && p_quality <= 1.0f), ERR_INVALID_PARAMETER, "The WebP lossy quality was set to " + rtos(p_quality) + ", which is not valid. WebP lossy quality must be between 0.0 and 1.0 (inclusive).");
+ ERR_FAIL_COND_V_MSG(p_lossy && !(0.0f <= p_quality && p_quality <= 1.0f), ERR_INVALID_PARAMETER, vformat("The WebP lossy quality was set to %f, which is not valid. WebP lossy quality must be between 0.0 and 1.0 (inclusive).", p_quality));
return save_webp_func(p_path, Ref<Image>((Image *)this), p_lossy, p_quality);
}
@@ -2660,7 +2660,7 @@ Vector<uint8_t> Image::save_webp_to_buffer(const bool p_lossy, const float p_qua
if (save_webp_buffer_func == nullptr) {
return Vector<uint8_t>();
}
- ERR_FAIL_COND_V_MSG(p_lossy && !(0.0f <= p_quality && p_quality <= 1.0f), Vector<uint8_t>(), "The WebP lossy quality was set to " + rtos(p_quality) + ", which is not valid. WebP lossy quality must be between 0.0 and 1.0 (inclusive).");
+ ERR_FAIL_COND_V_MSG(p_lossy && !(0.0f <= p_quality && p_quality <= 1.0f), Vector<uint8_t>(), vformat("The WebP lossy quality was set to %f, which is not valid. WebP lossy quality must be between 0.0 and 1.0 (inclusive).", p_quality));
return save_webp_buffer_func(Ref<Image>((Image *)this), p_lossy, p_quality);
}
diff --git a/core/io/image_loader.cpp b/core/io/image_loader.cpp
index 5724cd66a9..8e87569d7c 100644
--- a/core/io/image_loader.cpp
+++ b/core/io/image_loader.cpp
@@ -89,7 +89,7 @@ Error ImageLoader::load_image(const String &p_file, Ref<Image> p_image, Ref<File
if (f.is_null()) {
Error err;
f = FileAccess::open(p_file, FileAccess::READ, &err);
- ERR_FAIL_COND_V_MSG(f.is_null(), err, "Error opening file '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(f.is_null(), err, vformat("Error opening file '%s'.", p_file));
}
String extension = p_file.get_extension();
@@ -100,7 +100,7 @@ Error ImageLoader::load_image(const String &p_file, Ref<Image> p_image, Ref<File
}
Error err = loader.write[i]->load_image(p_image, f, p_flags, p_scale);
if (err != OK) {
- ERR_PRINT("Error loading image: " + p_file);
+ ERR_PRINT(vformat("Error loading image: '%s'.", p_file));
}
if (err != ERR_FILE_UNRECOGNIZED) {
diff --git a/core/io/ip.cpp b/core/io/ip.cpp
index cf152cb41a..381436dc7a 100644
--- a/core/io/ip.cpp
+++ b/core/io/ip.cpp
@@ -203,7 +203,7 @@ IPAddress IP::get_resolve_item_address(ResolverID p_id) const {
MutexLock lock(resolver->mutex);
if (resolver->queue[p_id].status.get() != IP::RESOLVER_STATUS_DONE) {
- ERR_PRINT("Resolve of '" + resolver->queue[p_id].hostname + "'' didn't complete yet.");
+ ERR_PRINT(vformat("Resolve of '%s' didn't complete yet.", resolver->queue[p_id].hostname));
return IPAddress();
}
@@ -222,7 +222,7 @@ Array IP::get_resolve_item_addresses(ResolverID p_id) const {
MutexLock lock(resolver->mutex);
if (resolver->queue[p_id].status.get() != IP::RESOLVER_STATUS_DONE) {
- ERR_PRINT("Resolve of '" + resolver->queue[p_id].hostname + "'' didn't complete yet.");
+ ERR_PRINT(vformat("Resolve of '%s' didn't complete yet.", resolver->queue[p_id].hostname));
return Array();
}
diff --git a/core/io/json.cpp b/core/io/json.cpp
index 0bbe79a0d1..989b292cbd 100644
--- a/core/io/json.cpp
+++ b/core/io/json.cpp
@@ -1404,7 +1404,7 @@ Error ResourceFormatSaverJSON::save(const Ref<Resource> &p_resource, const Strin
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err);
- ERR_FAIL_COND_V_MSG(err, err, "Cannot save json '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err, err, vformat("Cannot save json '%s'.", p_path));
file->store_string(source);
if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) {
diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp
index 8d6334b4ab..d425af0afd 100644
--- a/core/io/marshalls.cpp
+++ b/core/io/marshalls.cpp
@@ -700,9 +700,9 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
if (str == "script" && value.get_type() != Variant::NIL) {
ERR_FAIL_COND_V_MSG(value.get_type() != Variant::STRING, ERR_INVALID_DATA, "Invalid value for \"script\" property, expected script path as String.");
String path = value;
- ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, "Invalid script path: '" + path + "'.");
+ ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, vformat("Invalid script path: '%s'.", path));
Ref<Script> script = ResourceLoader::load(path, "Script");
- ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, "Can't load script at path: '" + path + "'.");
+ ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, vformat("Can't load script at path: '%s'.", path));
obj->set_script(script);
} else {
obj->set(str, value);
@@ -822,9 +822,9 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
builtin_type = Variant::OBJECT;
if (p_allow_objects) {
- ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, "Invalid script path: '" + path + "'.");
+ ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://") || !ResourceLoader::exists(path, "Script"), ERR_INVALID_DATA, vformat("Invalid script path: '%s'.", path));
script = ResourceLoader::load(path, "Script");
- ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, "Can't load script at path: '" + path + "'.");
+ ERR_FAIL_COND_V_MSG(script.is_null(), ERR_INVALID_DATA, vformat("Can't load script at path: '%s'.", path));
class_name = script->get_instance_base_type();
} else {
class_name = EncodedObjectAsID::get_class_static();
diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp
index 270f154887..9127d65abd 100644
--- a/core/io/pck_packer.cpp
+++ b/core/io/pck_packer.cpp
@@ -86,7 +86,7 @@ Error PCKPacker::pck_start(const String &p_pck_path, int p_alignment, const Stri
enc_dir = p_encrypt_directory;
file = FileAccess::open(p_pck_path, FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(file.is_null(), ERR_CANT_CREATE, "Can't open file to write: " + String(p_pck_path) + ".");
+ ERR_FAIL_COND_V_MSG(file.is_null(), ERR_CANT_CREATE, vformat("Can't open file to write: '%s'.", String(p_pck_path)));
alignment = p_alignment;
diff --git a/core/io/plist.cpp b/core/io/plist.cpp
index de3ab52644..9fb0b461f1 100644
--- a/core/io/plist.cpp
+++ b/core/io/plist.cpp
@@ -452,7 +452,7 @@ PList::PList() {
PList::PList(const String &p_string) {
String err_str;
bool ok = load_string(p_string, err_str);
- ERR_FAIL_COND_MSG(!ok, "PList: " + err_str);
+ ERR_FAIL_COND_MSG(!ok, vformat("PList: %s.", err_str));
}
uint64_t PList::read_bplist_var_size_int(Ref<FileAccess> p_file, uint8_t p_size) {
diff --git a/core/io/remote_filesystem_client.cpp b/core/io/remote_filesystem_client.cpp
index ee44f19ec1..7985a59d43 100644
--- a/core/io/remote_filesystem_client.cpp
+++ b/core/io/remote_filesystem_client.cpp
@@ -98,7 +98,7 @@ Error RemoteFilesystemClient::_store_file(const String &p_path, const LocalVecto
}
Ref<FileAccess> f = FileAccess::open(full_path, FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Unable to open file for writing to remote filesystem cache: " + p_path);
+ ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, vformat("Unable to open file for writing to remote filesystem cache: '%s'.", p_path));
f->store_buffer(p_file.ptr(), p_file.size());
Error err = f->get_error();
if (err) {
@@ -117,10 +117,10 @@ Error RemoteFilesystemClient::_store_cache_file(const Vector<FileCache> &p_cache
String full_path = cache_path.path_join(FILES_CACHE_FILE);
String base_file_dir = full_path.get_base_dir();
Error err = DirAccess::make_dir_recursive_absolute(base_file_dir);
- ERR_FAIL_COND_V_MSG(err != OK && err != ERR_ALREADY_EXISTS, err, "Unable to create base directory to store cache file: " + base_file_dir);
+ ERR_FAIL_COND_V_MSG(err != OK && err != ERR_ALREADY_EXISTS, err, vformat("Unable to create base directory to store cache file: '%s'.", base_file_dir));
Ref<FileAccess> f = FileAccess::open(full_path, FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Unable to open the remote cache file for writing: " + full_path);
+ ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, vformat("Unable to open the remote cache file for writing: '%s'.", full_path));
f->store_line(itos(FILESYSTEM_CACHE_VERSION));
for (int i = 0; i < p_cache.size(); i++) {
String l = p_cache[i].path + "::" + itos(p_cache[i].server_modified_time) + "::" + itos(p_cache[i].modified_time);
@@ -153,10 +153,10 @@ Error RemoteFilesystemClient::_synchronize_with_server(const String &p_host, int
tcp_client.instantiate();
IPAddress ip = p_host.is_valid_ip_address() ? IPAddress(p_host) : IP::get_singleton()->resolve_hostname(p_host);
- ERR_FAIL_COND_V_MSG(!ip.is_valid(), ERR_INVALID_PARAMETER, "Unable to resolve remote filesystem server hostname: " + p_host);
+ ERR_FAIL_COND_V_MSG(!ip.is_valid(), ERR_INVALID_PARAMETER, vformat("Unable to resolve remote filesystem server hostname: '%s'.", p_host));
print_verbose(vformat("Remote Filesystem: Connecting to host %s, port %d.", ip, p_port));
Error err = tcp_client->connect_to_host(ip, p_port);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to open connection to remote file server (" + String(p_host) + ", port " + itos(p_port) + ") failed.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Unable to open connection to remote file server (%s, port %d) failed.", String(p_host), p_port));
while (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTING) {
tcp_client->poll();
@@ -164,7 +164,7 @@ Error RemoteFilesystemClient::_synchronize_with_server(const String &p_host, int
}
if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
- ERR_FAIL_V_MSG(ERR_CANT_CONNECT, "Connection to remote file server (" + String(p_host) + ", port " + itos(p_port) + ") failed.");
+ ERR_FAIL_V_MSG(ERR_CANT_CONNECT, vformat("Connection to remote file server (%s, port %d) failed.", String(p_host), p_port));
}
// Connection OK, now send the current file state.
@@ -283,7 +283,7 @@ Error RemoteFilesystemClient::_synchronize_with_server(const String &p_host, int
err = tcp_client->get_data(file_buffer.ptr(), file_size);
if (err != OK) {
- ERR_PRINT("Error retrieving file from remote filesystem: " + file);
+ ERR_PRINT(vformat("Error retrieving file from remote filesystem: '%s'.", file));
server_disconnected = true;
}
diff --git a/core/io/resource.cpp b/core/io/resource.cpp
index 1f2ebbbd50..5b2af556fe 100644
--- a/core/io/resource.cpp
+++ b/core/io/resource.cpp
@@ -78,7 +78,7 @@ void Resource::set_path(const String &p_path, bool p_take_over) {
existing->path_cache = String();
ResourceCache::resources.erase(p_path);
} else {
- ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion).");
+ ERR_FAIL_MSG(vformat("Another resource is loaded from path '%s' (possible cyclic resource inclusion).", p_path));
}
}
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index 7d5b305eb0..a714a065b5 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -413,7 +413,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) {
//always use internal cache for loading internal resources
if (!internal_index_cache.has(path)) {
- WARN_PRINT(String("Couldn't load resource (no cache): " + path).utf8().get_data());
+ WARN_PRINT(vformat("Couldn't load resource (no cache): %s.", path));
r_v = Variant();
} else {
r_v = internal_index_cache[path];
@@ -437,7 +437,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) {
Ref<Resource> res = ResourceLoader::load(path, exttype, cache_mode_for_external);
if (res.is_null()) {
- WARN_PRINT(String("Couldn't load resource: " + path).utf8().get_data());
+ WARN_PRINT(vformat("Couldn't load resource: %s.", path));
}
r_v = res;
@@ -460,7 +460,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) {
ResourceLoader::notify_dependency_error(local_path, external_resources[erindex].path, external_resources[erindex].type);
} else {
error = ERR_FILE_MISSING_DEPENDENCIES;
- ERR_FAIL_V_MSG(error, "Can't load dependency: " + external_resources[erindex].path + ".");
+ ERR_FAIL_V_MSG(error, vformat("Can't load dependency: '%s'.", external_resources[erindex].path));
}
}
} else {
@@ -706,7 +706,7 @@ Error ResourceLoaderBinary::load() {
ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type);
} else {
error = ERR_FILE_MISSING_DEPENDENCIES;
- ERR_FAIL_V_MSG(error, "Can't load dependency: " + path + ".");
+ ERR_FAIL_V_MSG(error, vformat("Can't load dependency: '%s'.", path));
}
}
}
@@ -782,7 +782,7 @@ Error ResourceLoaderBinary::load() {
obj = missing_resource;
} else {
error = ERR_FILE_CORRUPT;
- ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource of unrecognized type in file: " + t + ".");
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, vformat("'%s': Resource of unrecognized type in file: '%s'.", local_path, t));
}
}
@@ -791,7 +791,7 @@ Error ResourceLoaderBinary::load() {
String obj_class = obj->get_class();
error = ERR_FILE_CORRUPT;
memdelete(obj); //bye
- ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource type in resource field not a resource, type is: " + obj_class + ".");
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, vformat("'%s': Resource type in resource field not a resource, type is: %s.", local_path, obj_class));
}
res = Ref<Resource>(r);
@@ -1001,7 +1001,7 @@ void ResourceLoaderBinary::open(Ref<FileAccess> p_f, bool p_no_resources, bool p
error = fac->open_after_magic(f);
if (error != OK) {
f.unref();
- ERR_FAIL_MSG("Failed to open binary resource file: " + local_path + ".");
+ ERR_FAIL_MSG(vformat("Failed to open binary resource file: '%s'.", local_path));
}
f = fac;
@@ -1009,7 +1009,7 @@ void ResourceLoaderBinary::open(Ref<FileAccess> p_f, bool p_no_resources, bool p
// Not normal.
error = ERR_FILE_UNRECOGNIZED;
f.unref();
- ERR_FAIL_MSG("Unrecognized binary resource file: " + local_path + ".");
+ ERR_FAIL_MSG(vformat("Unrecognized binary resource file: '%s'.", local_path));
}
bool big_endian = f->get_32();
@@ -1095,10 +1095,10 @@ void ResourceLoaderBinary::open(Ref<FileAccess> p_f, bool p_no_resources, bool p
#ifdef TOOLS_ENABLED
// Silence a warning that can happen during the initial filesystem scan due to cache being regenerated.
if (ResourceLoader::get_resource_uid(res_path) != er.uid) {
- WARN_PRINT(String(res_path + ": In external resource #" + itos(i) + ", invalid UID: " + ResourceUID::get_singleton()->id_to_text(er.uid) + " - using text path instead: " + er.path).utf8().get_data());
+ WARN_PRINT(vformat("'%s': In external resource #%d, invalid UID: '%s' - using text path instead: '%s'.", res_path, i, ResourceUID::get_singleton()->id_to_text(er.uid), er.path));
}
#else
- WARN_PRINT(String(res_path + ": In external resource #" + itos(i) + ", invalid UID: " + ResourceUID::get_singleton()->id_to_text(er.uid) + " - using text path instead: " + er.path).utf8().get_data());
+ WARN_PRINT(vformat("'%s': In external resource #%d, invalid UID: '%s' - using text path instead: '%s'.", res_path, i, ResourceUID::get_singleton()->id_to_text(er.uid), er.path));
#endif
}
}
@@ -1122,7 +1122,7 @@ void ResourceLoaderBinary::open(Ref<FileAccess> p_f, bool p_no_resources, bool p
if (f->eof_reached()) {
error = ERR_FILE_CORRUPT;
f.unref();
- ERR_FAIL_MSG("Premature end of file (EOF): " + local_path + ".");
+ ERR_FAIL_MSG(vformat("Premature end of file (EOF): '%s'.", local_path));
}
}
@@ -1226,7 +1226,7 @@ Ref<Resource> ResourceFormatLoaderBinary::load(const String &p_path, const Strin
Error err;
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
- ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, Ref<Resource>(), vformat("Cannot open file '%s'.", p_path));
ResourceLoaderBinary loader;
switch (p_cache_mode) {
@@ -1303,7 +1303,7 @@ bool ResourceFormatLoaderBinary::handles_type(const String &p_type) const {
void ResourceFormatLoaderBinary::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_MSG(f.is_null(), "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_MSG(f.is_null(), vformat("Cannot open file '%s'.", p_path));
ResourceLoaderBinary loader;
loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
@@ -1313,7 +1313,7 @@ void ResourceFormatLoaderBinary::get_dependencies(const String &p_path, List<Str
Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, const HashMap<String, String> &p_map) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Cannot open file '%s'.", p_path));
Ref<FileAccess> fw;
@@ -1326,23 +1326,23 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons
Ref<FileAccessCompressed> fac;
fac.instantiate();
Error err = fac->open_after_magic(f);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot open file '%s'.", p_path));
f = fac;
Ref<FileAccessCompressed> facw;
facw.instantiate();
facw->configure("RSCC");
err = facw->open_internal(p_path + ".depren", FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(err, ERR_FILE_CORRUPT, "Cannot create file '" + p_path + ".depren'.");
+ ERR_FAIL_COND_V_MSG(err, ERR_FILE_CORRUPT, vformat("Cannot create file '%s.depren'.", p_path));
fw = facw;
} else if (header[0] != 'R' || header[1] != 'S' || header[2] != 'R' || header[3] != 'C') {
// Not normal.
- ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, "Unrecognized binary resource file '" + local_path + "'.");
+ ERR_FAIL_V_MSG(ERR_FILE_UNRECOGNIZED, vformat("Unrecognized binary resource file '%s'.", local_path));
} else {
fw = FileAccess::open(p_path + ".depren", FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(fw.is_null(), ERR_CANT_CREATE, "Cannot create file '" + p_path + ".depren'.");
+ ERR_FAIL_COND_V_MSG(fw.is_null(), ERR_CANT_CREATE, vformat("Cannot create file '%s.depren'.", p_path));
uint8_t magic[4] = { 'R', 'S', 'R', 'C' };
fw->store_buffer(magic, 4);
@@ -1374,12 +1374,12 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons
// Use the old approach.
- WARN_PRINT("This file is old, so it can't refactor dependencies, opening and resaving '" + p_path + "'.");
+ WARN_PRINT(vformat("This file is old, so it can't refactor dependencies, opening and resaving '%s'.", p_path));
Error err;
f = FileAccess::open(p_path, FileAccess::READ, &err);
- ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_OPEN, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_OPEN, vformat("Cannot open file '%s'.", p_path));
ResourceLoaderBinary loader;
loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
@@ -1525,7 +1525,7 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons
void ResourceFormatLoaderBinary::get_classes_used(const String &p_path, HashSet<StringName> *r_classes) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_MSG(f.is_null(), "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_MSG(f.is_null(), vformat("Cannot open file '%s'.", p_path));
ResourceLoaderBinary loader;
loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
@@ -2029,7 +2029,7 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant
if (!p_main && (!bundle_resources) && !res->is_built_in()) {
if (res->get_path() == path) {
- ERR_PRINT("Circular reference to resource being saved found: '" + local_path + "' will be null next time it's loaded.");
+ ERR_PRINT(vformat("Circular reference to resource being saved found: '%s' will be null next time it's loaded.", local_path));
return;
}
int idx = external_resources.size();
@@ -2155,7 +2155,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
f = FileAccess::open(p_path, FileAccess::WRITE, &err);
}
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot create file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot create file '%s'.", p_path));
relative_paths = p_flags & ResourceSaver::FLAG_RELATIVE_PATHS;
skip_editor = p_flags & ResourceSaver::FLAG_OMIT_EDITOR_PROPERTIES;
@@ -2386,7 +2386,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
Error ResourceFormatSaverBinaryInstance::set_uid(const String &p_path, ResourceUID::ID p_uid) {
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Cannot open file '%s'.", p_path));
Ref<FileAccess> fw;
@@ -2399,14 +2399,14 @@ Error ResourceFormatSaverBinaryInstance::set_uid(const String &p_path, ResourceU
Ref<FileAccessCompressed> fac;
fac.instantiate();
Error err = fac->open_after_magic(f);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot open file '%s'.", p_path));
f = fac;
Ref<FileAccessCompressed> facw;
facw.instantiate();
facw->configure("RSCC");
err = facw->open_internal(p_path + ".uidren", FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(err, ERR_FILE_CORRUPT, "Cannot create file '" + p_path + ".uidren'.");
+ ERR_FAIL_COND_V_MSG(err, ERR_FILE_CORRUPT, vformat("Cannot create file '%s.uidren'.", p_path));
fw = facw;
@@ -2415,7 +2415,7 @@ Error ResourceFormatSaverBinaryInstance::set_uid(const String &p_path, ResourceU
return ERR_FILE_UNRECOGNIZED;
} else {
fw = FileAccess::open(p_path + ".uidren", FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(fw.is_null(), ERR_CANT_CREATE, "Cannot create file '" + p_path + ".uidren'.");
+ ERR_FAIL_COND_V_MSG(fw.is_null(), ERR_CANT_CREATE, vformat("Cannot create file '%s.uidren'.", p_path));
uint8_t magich[4] = { 'R', 'S', 'R', 'C' };
fw->store_buffer(magich, 4);
@@ -2446,7 +2446,7 @@ Error ResourceFormatSaverBinaryInstance::set_uid(const String &p_path, ResourceU
// Use the old approach.
- WARN_PRINT("This file is old, so it does not support UIDs, opening and resaving '" + p_path + "'.");
+ WARN_PRINT(vformat("This file is old, so it does not support UIDs, opening and resaving '%s'.", p_path));
return ERR_UNAVAILABLE;
}
diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp
index 1e481d3aaa..b72b19ae59 100644
--- a/core/io/resource_importer.cpp
+++ b/core/io/resource_importer.cpp
@@ -77,7 +77,7 @@ Error ResourceFormatImporter::_get_path_and_type(const String &p_path, PathAndTy
if (err == ERR_FILE_EOF) {
return OK;
} else if (err != OK) {
- ERR_PRINT("ResourceFormatImporter::load - " + p_path + ".import:" + itos(lines) + " error: " + error_text);
+ ERR_PRINT(vformat("ResourceFormatImporter::load - %s.import:%d error: %s.", p_path, lines, error_text));
return err;
}
@@ -337,7 +337,7 @@ void ResourceFormatImporter::get_internal_resource_path_list(const String &p_pat
if (err == ERR_FILE_EOF) {
return;
} else if (err != OK) {
- ERR_PRINT("ResourceFormatImporter::get_internal_resource_path_list - " + p_path + ".import:" + itos(lines) + " error: " + error_text);
+ ERR_PRINT(vformat("ResourceFormatImporter::get_internal_resource_path_list - %s.import:%d error: %s.", p_path, lines, error_text));
return;
}
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index df12a0a3fd..97e6f6da06 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -164,7 +164,7 @@ Ref<Resource> ResourceFormatLoader::load(const String &p_path, const String &p_o
}
}
- ERR_FAIL_V_MSG(Ref<Resource>(), "Failed to load resource '" + p_path + "'. ResourceFormatLoader::load was not implemented for this resource type.");
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Failed to load resource '%s'. ResourceFormatLoader::load was not implemented for this resource type.", p_path));
}
void ResourceFormatLoader::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
@@ -1165,7 +1165,7 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem
// An extra remap may still be necessary afterwards due to the text -> binary converter on export.
String locale = TranslationServer::get_singleton()->get_locale();
- ERR_FAIL_COND_V_MSG(locale.length() < 2, p_path, "Could not remap path '" + p_path + "' for translation as configured locale '" + locale + "' is invalid.");
+ ERR_FAIL_COND_V_MSG(locale.length() < 2, p_path, vformat("Could not remap path '%s' for translation as configured locale '%s' is invalid.", p_path, locale));
Vector<String> &res_remaps = *translation_remaps.getptr(new_path);
@@ -1224,7 +1224,7 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem
if (err == ERR_FILE_EOF) {
break;
} else if (err != OK) {
- ERR_PRINT("Parse error: " + p_path + ".remap:" + itos(lines) + " error: " + error_text + ".");
+ ERR_PRINT(vformat("Parse error: %s.remap:%d error: %s.", p_path, lines, error_text));
break;
}
diff --git a/core/io/resource_saver.cpp b/core/io/resource_saver.cpp
index d6f00719f8..b0c551abd2 100644
--- a/core/io/resource_saver.cpp
+++ b/core/io/resource_saver.cpp
@@ -100,7 +100,7 @@ void ResourceFormatSaver::_bind_methods() {
}
Error ResourceSaver::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) {
- ERR_FAIL_COND_V_MSG(p_resource.is_null(), ERR_INVALID_PARAMETER, "Can't save empty resource to path '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(p_resource.is_null(), ERR_INVALID_PARAMETER, vformat("Can't save empty resource to path '%s'.", p_path));
String path = p_path;
if (path.is_empty()) {
path = p_resource->get_path();
diff --git a/core/io/translation_loader_po.cpp b/core/io/translation_loader_po.cpp
index e7b2accf2b..54979a458c 100644
--- a/core/io/translation_loader_po.cpp
+++ b/core/io/translation_loader_po.cpp
@@ -171,14 +171,14 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
// If we reached last line and it's not a content line, break, otherwise let processing that last loop
if (is_eof && l.is_empty()) {
if (status == STATUS_READING_ID || status == STATUS_READING_CONTEXT || (status == STATUS_READING_PLURAL && plural_index != plural_forms - 1)) {
- ERR_FAIL_V_MSG(Ref<Resource>(), "Unexpected EOF while reading PO file at: " + path + ":" + itos(line));
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Unexpected EOF while reading PO file at: %s:%d.", path, line));
} else {
break;
}
}
if (l.begins_with("msgctxt")) {
- ERR_FAIL_COND_V_MSG(status != STATUS_READING_STRING && status != STATUS_READING_PLURAL, Ref<Resource>(), "Unexpected 'msgctxt', was expecting 'msgid_plural' or 'msgstr' before 'msgctxt' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(status != STATUS_READING_STRING && status != STATUS_READING_PLURAL, Ref<Resource>(), vformat("Unexpected 'msgctxt', was expecting 'msgid_plural' or 'msgstr' before 'msgctxt' while parsing: %s:%d.", path, line));
// In PO file, "msgctxt" appears before "msgid". If we encounter a "msgctxt", we add what we have read
// and set "entered_context" to true to prevent adding twice.
@@ -186,7 +186,7 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
if (status == STATUS_READING_STRING) {
translation->add_message(msg_id, msg_str, msg_context);
} else if (status == STATUS_READING_PLURAL) {
- ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), vformat("Number of 'msgstr[]' doesn't match with number of plural forms: %s:%d.", path, line));
translation->add_plural_message(msg_id, msgs_plural, msg_context);
}
}
@@ -198,9 +198,9 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
if (l.begins_with("msgid_plural")) {
if (plural_forms == 0) {
- ERR_FAIL_V_MSG(Ref<Resource>(), "PO file uses 'msgid_plural' but 'Plural-Forms' is invalid or missing in header: " + path + ":" + itos(line));
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("PO file uses 'msgid_plural' but 'Plural-Forms' is invalid or missing in header: %s:%d.", path, line));
} else if (status != STATUS_READING_ID) {
- ERR_FAIL_V_MSG(Ref<Resource>(), "Unexpected 'msgid_plural', was expecting 'msgid' before 'msgid_plural' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Unexpected 'msgid_plural', was expecting 'msgid' before 'msgid_plural' while parsing: %s:%d.", path, line));
}
// We don't record the message in "msgid_plural" itself as tr_n(), TTRN(), RTRN() interfaces provide the plural string already.
// We just have to reset variables related to plurals for "msgstr[]" later on.
@@ -210,14 +210,14 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
msgs_plural.resize(plural_forms);
status = STATUS_READING_PLURAL;
} else if (l.begins_with("msgid")) {
- ERR_FAIL_COND_V_MSG(status == STATUS_READING_ID, Ref<Resource>(), "Unexpected 'msgid', was expecting 'msgstr' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(status == STATUS_READING_ID, Ref<Resource>(), vformat("Unexpected 'msgid', was expecting 'msgstr' while parsing: %s:%d.", path, line));
if (!msg_id.is_empty()) {
if (!skip_this && !entered_context) {
if (status == STATUS_READING_STRING) {
translation->add_message(msg_id, msg_str, msg_context);
} else if (status == STATUS_READING_PLURAL) {
- ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), vformat("Number of 'msgstr[]' doesn't match with number of plural forms: %s:%d.", path, line));
translation->add_plural_message(msg_id, msgs_plural, msg_context);
}
}
@@ -246,11 +246,11 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
}
if (l.begins_with("msgstr[")) {
- ERR_FAIL_COND_V_MSG(status != STATUS_READING_PLURAL, Ref<Resource>(), "Unexpected 'msgstr[]', was expecting 'msgid_plural' before 'msgstr[]' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(status != STATUS_READING_PLURAL, Ref<Resource>(), vformat("Unexpected 'msgstr[]', was expecting 'msgid_plural' before 'msgstr[]' while parsing: %s:%d.", path, line));
plural_index++; // Increment to add to the next slot in vector msgs_plural.
l = l.substr(9, l.length()).strip_edges();
} else if (l.begins_with("msgstr")) {
- ERR_FAIL_COND_V_MSG(status != STATUS_READING_ID, Ref<Resource>(), "Unexpected 'msgstr', was expecting 'msgid' before 'msgstr' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(status != STATUS_READING_ID, Ref<Resource>(), vformat("Unexpected 'msgstr', was expecting 'msgid' before 'msgstr' while parsing: %s:%d.", path, line));
l = l.substr(6, l.length()).strip_edges();
status = STATUS_READING_STRING;
}
@@ -263,7 +263,7 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
continue; // Nothing to read or comment.
}
- ERR_FAIL_COND_V_MSG(!l.begins_with("\"") || status == STATUS_NONE, Ref<Resource>(), "Invalid line '" + l + "' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(!l.begins_with("\"") || status == STATUS_NONE, Ref<Resource>(), vformat("Invalid line '%s' while parsing: %s:%d.", l, path, line));
l = l.substr(1, l.length());
// Find final quote, ignoring escaped ones (\").
@@ -285,7 +285,7 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
escape_next = false;
}
- ERR_FAIL_COND_V_MSG(end_pos == -1, Ref<Resource>(), "Expected '\"' at end of message while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(end_pos == -1, Ref<Resource>(), vformat("Expected '\"' at end of message while parsing: %s:%d.", path, line));
l = l.substr(0, end_pos);
l = l.c_unescape();
@@ -297,7 +297,7 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
} else if (status == STATUS_READING_CONTEXT) {
msg_context += l;
} else if (status == STATUS_READING_PLURAL && plural_index >= 0) {
- ERR_FAIL_COND_V_MSG(plural_index >= plural_forms, Ref<Resource>(), "Unexpected plural form while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(plural_index >= plural_forms, Ref<Resource>(), vformat("Unexpected plural form while parsing: %s:%d.", path, line));
msgs_plural.write[plural_index] = msgs_plural[plural_index] + l;
}
@@ -315,13 +315,13 @@ Ref<Resource> TranslationLoaderPO::load_translation(Ref<FileAccess> f, Error *r_
}
} else if (status == STATUS_READING_PLURAL) {
if (!skip_this && !msg_id.is_empty()) {
- ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line));
+ ERR_FAIL_COND_V_MSG(plural_index != plural_forms - 1, Ref<Resource>(), vformat("Number of 'msgstr[]' doesn't match with number of plural forms: %s:%d.", path, line));
translation->add_plural_message(msg_id, msgs_plural, msg_context);
}
}
}
- ERR_FAIL_COND_V_MSG(config.is_empty(), Ref<Resource>(), "No config found in file: " + path + ".");
+ ERR_FAIL_COND_V_MSG(config.is_empty(), Ref<Resource>(), vformat("No config found in file: '%s'.", path));
Vector<String> configs = config.split("\n");
for (int i = 0; i < configs.size(); i++) {
@@ -351,7 +351,7 @@ Ref<Resource> TranslationLoaderPO::load(const String &p_path, const String &p_or
}
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(f.is_null(), Ref<Resource>(), "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(f.is_null(), Ref<Resource>(), vformat("Cannot open file '%s'.", p_path));
return load_translation(f, r_error);
}
diff --git a/core/io/xml_parser.cpp b/core/io/xml_parser.cpp
index 69cb8d9f64..8021aa303e 100644
--- a/core/io/xml_parser.cpp
+++ b/core/io/xml_parser.cpp
@@ -431,7 +431,7 @@ String XMLParser::get_named_attribute_value(const String &p_name) const {
}
}
- ERR_FAIL_COND_V_MSG(idx < 0, "", "Attribute not found: " + p_name + ".");
+ ERR_FAIL_COND_V_MSG(idx < 0, "", vformat("Attribute not found: '%s'.", p_name));
return attributes[idx].value;
}
@@ -495,7 +495,7 @@ Error XMLParser::open(const String &p_path) {
Error err;
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open file '" + p_path + "'.");
+ ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Cannot open file '%s'.", p_path));
length = file->get_length();
ERR_FAIL_COND_V(length < 1, ERR_FILE_CORRUPT);
diff --git a/core/math/expression.cpp b/core/math/expression.cpp
index 6b8205a726..5eb524efed 100644
--- a/core/math/expression.cpp
+++ b/core/math/expression.cpp
@@ -1493,7 +1493,7 @@ Error Expression::parse(const String &p_expression, const Vector<String> &p_inpu
}
Variant Expression::execute(const Array &p_inputs, Object *p_base, bool p_show_error, bool p_const_calls_only) {
- ERR_FAIL_COND_V_MSG(error_set, Variant(), "There was previously a parse error: " + error_str + ".");
+ ERR_FAIL_COND_V_MSG(error_set, Variant(), vformat("There was previously a parse error: %s.", error_str));
execution_error = false;
Variant output;
diff --git a/core/math/rect2.cpp b/core/math/rect2.cpp
index 20b5b05f5e..2b7bb9bc33 100644
--- a/core/math/rect2.cpp
+++ b/core/math/rect2.cpp
@@ -285,7 +285,7 @@ next4:
}
Rect2::operator String() const {
- return "[P: " + position.operator String() + ", S: " + size + "]";
+ return "[P: " + position.operator String() + ", S: " + size.operator String() + "]";
}
Rect2::operator Rect2i() const {
diff --git a/core/math/vector2.cpp b/core/math/vector2.cpp
index 5ec9aeb9c2..b54e185b4d 100644
--- a/core/math/vector2.cpp
+++ b/core/math/vector2.cpp
@@ -205,7 +205,7 @@ bool Vector2::is_finite() const {
}
Vector2::operator String() const {
- return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ")";
+ return "(" + String::num_real(x, true) + ", " + String::num_real(y, true) + ")";
}
Vector2::operator Vector2i() const {
diff --git a/core/math/vector3.cpp b/core/math/vector3.cpp
index e1794222ad..7560528bb9 100644
--- a/core/math/vector3.cpp
+++ b/core/math/vector3.cpp
@@ -167,7 +167,7 @@ bool Vector3::is_finite() const {
}
Vector3::operator String() const {
- return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ", " + String::num_real(z, false) + ")";
+ return "(" + String::num_real(x, true) + ", " + String::num_real(y, true) + ", " + String::num_real(z, true) + ")";
}
Vector3::operator Vector3i() const {
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index ca08ec0dcb..3ec1f6e7b4 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -354,7 +354,7 @@ StringName ClassDB::get_compatibility_remapped_class(const StringName &p_class)
StringName ClassDB::_get_parent_class(const StringName &p_class) {
ClassInfo *ti = classes.getptr(p_class);
- ERR_FAIL_NULL_V_MSG(ti, StringName(), "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, StringName(), vformat("Cannot get class '%s'.", String(p_class)));
return ti->inherits;
}
@@ -369,7 +369,7 @@ ClassDB::APIType ClassDB::get_api_type(const StringName &p_class) {
ClassInfo *ti = classes.getptr(p_class);
- ERR_FAIL_NULL_V_MSG(ti, API_NONE, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, API_NONE, vformat("Cannot get class '%s'.", String(p_class)));
return ti->api;
}
@@ -392,7 +392,7 @@ uint32_t ClassDB::get_api_hash(APIType p_api) {
for (const StringName &E : class_list) {
ClassInfo *t = classes.getptr(E);
- ERR_FAIL_NULL_V_MSG(t, 0, "Cannot get class '" + String(E) + "'.");
+ ERR_FAIL_NULL_V_MSG(t, 0, vformat("Cannot get class '%s'.", String(E)));
if (t->api != p_api || !t->exposed) {
continue;
}
@@ -549,14 +549,14 @@ Object *ClassDB::_instantiate_internal(const StringName &p_class, bool p_require
ti = classes.getptr(compat_classes[p_class]);
}
}
- ERR_FAIL_NULL_V_MSG(ti, nullptr, "Cannot get class '" + String(p_class) + "'.");
- ERR_FAIL_COND_V_MSG(ti->disabled, nullptr, "Class '" + String(p_class) + "' is disabled.");
- ERR_FAIL_NULL_V_MSG(ti->creation_func, nullptr, "Class '" + String(p_class) + "' or its base class cannot be instantiated.");
+ ERR_FAIL_NULL_V_MSG(ti, nullptr, vformat("Cannot get class '%s'.", String(p_class)));
+ ERR_FAIL_COND_V_MSG(ti->disabled, nullptr, vformat("Class '%s' is disabled.", String(p_class)));
+ ERR_FAIL_NULL_V_MSG(ti->creation_func, nullptr, vformat("Class '%s' or its base class cannot be instantiated.", String(p_class)));
}
#ifdef TOOLS_ENABLED
if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
- ERR_PRINT("Class '" + String(p_class) + "' can only be instantiated by editor.");
+ ERR_PRINT(vformat("Class '%s' can only be instantiated by editor.", String(p_class)));
return nullptr;
}
#endif
@@ -655,8 +655,8 @@ ObjectGDExtension *ClassDB::get_placeholder_extension(const StringName &p_class)
ti = classes.getptr(compat_classes[p_class]);
}
}
- ERR_FAIL_NULL_V_MSG(ti, nullptr, "Cannot get class '" + String(p_class) + "'.");
- ERR_FAIL_COND_V_MSG(ti->disabled, nullptr, "Class '" + String(p_class) + "' is disabled.");
+ ERR_FAIL_NULL_V_MSG(ti, nullptr, vformat("Cannot get class '%s'.", String(p_class)));
+ ERR_FAIL_COND_V_MSG(ti->disabled, nullptr, vformat("Class '%s' is disabled.", String(p_class)));
}
// Make a "fake" extension to act as a placeholder.
@@ -736,9 +736,9 @@ void ClassDB::set_object_extension_instance(Object *p_object, const StringName &
ti = classes.getptr(compat_classes[p_class]);
}
}
- ERR_FAIL_NULL_MSG(ti, "Cannot get class '" + String(p_class) + "'.");
- ERR_FAIL_COND_MSG(ti->disabled, "Class '" + String(p_class) + "' is disabled.");
- ERR_FAIL_NULL_MSG(ti->gdextension, "Class '" + String(p_class) + "' has no native extension.");
+ ERR_FAIL_NULL_MSG(ti, vformat("Cannot get class '%s'.", String(p_class)));
+ ERR_FAIL_COND_MSG(ti->disabled, vformat("Class '%s' is disabled.", String(p_class)));
+ ERR_FAIL_NULL_MSG(ti->gdextension, vformat("Class '%s' has no native extension.", String(p_class)));
}
p_object->_extension = ti->gdextension;
@@ -757,7 +757,7 @@ bool ClassDB::can_instantiate(const StringName &p_class) {
ClassInfo *ti = classes.getptr(p_class);
if (!ti) {
if (!ScriptServer::is_global_class(p_class)) {
- ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_V_MSG(false, vformat("Cannot get class '%s'.", String(p_class)));
}
String path = ScriptServer::get_global_class_path(p_class);
Ref<Script> scr = ResourceLoader::load(path);
@@ -777,7 +777,7 @@ bool ClassDB::is_abstract(const StringName &p_class) {
ClassInfo *ti = classes.getptr(p_class);
if (!ti) {
if (!ScriptServer::is_global_class(p_class)) {
- ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_V_MSG(false, vformat("Cannot get class '%s'.", String(p_class)));
}
String path = ScriptServer::get_global_class_path(p_class);
Ref<Script> scr = ResourceLoader::load(path);
@@ -803,7 +803,7 @@ bool ClassDB::is_virtual(const StringName &p_class) {
ClassInfo *ti = classes.getptr(p_class);
if (!ti) {
if (!ScriptServer::is_global_class(p_class)) {
- ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_V_MSG(false, vformat("Cannot get class '%s'.", String(p_class)));
}
String path = ScriptServer::get_global_class_path(p_class);
Ref<Script> scr = ResourceLoader::load(path);
@@ -822,7 +822,7 @@ void ClassDB::_add_class2(const StringName &p_class, const StringName &p_inherit
const StringName &name = p_class;
- ERR_FAIL_COND_MSG(classes.has(name), "Class '" + String(p_class) + "' already exists.");
+ ERR_FAIL_COND_MSG(classes.has(name), vformat("Class '%s' already exists.", String(p_class)));
classes[name] = ClassInfo();
ClassInfo &ti = classes[name];
@@ -1330,7 +1330,7 @@ void ClassDB::add_signal(const StringName &p_class, const MethodInfo &p_signal)
#ifdef DEBUG_METHODS_ENABLED
ClassInfo *check = type;
while (check) {
- ERR_FAIL_COND_MSG(check->signal_map.has(sname), "Class '" + String(p_class) + "' already has signal '" + String(sname) + "'.");
+ ERR_FAIL_COND_MSG(check->signal_map.has(sname), vformat("Class '%s' already has signal '%s'.", String(p_class), String(sname)));
check = check->inherits_ptr;
}
#endif
@@ -1444,10 +1444,10 @@ void ClassDB::add_property(const StringName &p_class, const PropertyInfo &p_pinf
mb_set = get_method(p_class, p_setter);
#ifdef DEBUG_METHODS_ENABLED
- ERR_FAIL_NULL_MSG(mb_set, "Invalid setter '" + p_class + "::" + p_setter + "' for property '" + p_pinfo.name + "'.");
+ ERR_FAIL_NULL_MSG(mb_set, vformat("Invalid setter '%s::%s' for property '%s'.", p_class, p_setter, p_pinfo.name));
int exp_args = 1 + (p_index >= 0 ? 1 : 0);
- ERR_FAIL_COND_MSG(mb_set->get_argument_count() != exp_args, "Invalid function for setter '" + p_class + "::" + p_setter + " for property '" + p_pinfo.name + "'.");
+ ERR_FAIL_COND_MSG(mb_set->get_argument_count() != exp_args, vformat("Invalid function for setter '%s::%s' for property '%s'.", p_class, p_setter, p_pinfo.name));
#endif
}
@@ -1456,15 +1456,15 @@ void ClassDB::add_property(const StringName &p_class, const PropertyInfo &p_pinf
mb_get = get_method(p_class, p_getter);
#ifdef DEBUG_METHODS_ENABLED
- ERR_FAIL_NULL_MSG(mb_get, "Invalid getter '" + p_class + "::" + p_getter + "' for property '" + p_pinfo.name + "'.");
+ ERR_FAIL_NULL_MSG(mb_get, vformat("Invalid getter '%s::%s' for property '%s'.", p_class, p_getter, p_pinfo.name));
int exp_args = 0 + (p_index >= 0 ? 1 : 0);
- ERR_FAIL_COND_MSG(mb_get->get_argument_count() != exp_args, "Invalid function for getter '" + p_class + "::" + p_getter + "' for property: '" + p_pinfo.name + "'.");
+ ERR_FAIL_COND_MSG(mb_get->get_argument_count() != exp_args, vformat("Invalid function for getter '%s::%s' for property '%s'.", p_class, p_getter, p_pinfo.name));
#endif
}
#ifdef DEBUG_METHODS_ENABLED
- ERR_FAIL_COND_MSG(type->property_setget.has(p_pinfo.name), "Object '" + p_class + "' already has property '" + p_pinfo.name + "'.");
+ ERR_FAIL_COND_MSG(type->property_setget.has(p_pinfo.name), vformat("Object '%s' already has property '%s'.", p_class, p_pinfo.name));
#endif
OBJTYPE_WLOCK
@@ -1849,7 +1849,7 @@ void ClassDB::_bind_method_custom(const StringName &p_class, MethodBind *p_metho
ClassInfo *type = classes.getptr(p_class);
if (!type) {
- ERR_FAIL_MSG("Couldn't bind custom method '" + p_method->get_name() + "' for instance '" + p_class + "'.");
+ ERR_FAIL_MSG(vformat("Couldn't bind custom method '%s' for instance '%s'.", p_method->get_name(), p_class));
}
if (p_compatibility) {
@@ -1859,7 +1859,7 @@ void ClassDB::_bind_method_custom(const StringName &p_class, MethodBind *p_metho
if (type->method_map.has(p_method->get_name())) {
// overloading not supported
- ERR_FAIL_MSG("Method already bound '" + p_class + "::" + p_method->get_name() + "'.");
+ ERR_FAIL_MSG(vformat("Method already bound '%s::%s'.", p_class, p_method->get_name()));
}
#ifdef DEBUG_METHODS_ENABLED
@@ -1890,7 +1890,7 @@ MethodBind *ClassDB::_bind_vararg_method(MethodBind *p_bind, const StringName &p
if (type->method_map.has(p_name)) {
memdelete(bind);
// Overloading not supported
- ERR_FAIL_V_MSG(nullptr, "Method already bound: " + instance_type + "::" + p_name + ".");
+ ERR_FAIL_V_MSG(nullptr, vformat("Method already bound: '%s::%s'.", instance_type, p_name));
}
type->method_map[p_name] = bind;
#ifdef DEBUG_METHODS_ENABLED
@@ -1918,26 +1918,26 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_
#ifdef DEBUG_ENABLED
- ERR_FAIL_COND_V_MSG(!p_compatibility && has_method(instance_type, mdname), nullptr, "Class " + String(instance_type) + " already has a method " + String(mdname) + ".");
+ ERR_FAIL_COND_V_MSG(!p_compatibility && has_method(instance_type, mdname), nullptr, vformat("Class '%s' already has a method '%s'.", String(instance_type), String(mdname)));
#endif
ClassInfo *type = classes.getptr(instance_type);
if (!type) {
memdelete(p_bind);
- ERR_FAIL_V_MSG(nullptr, "Couldn't bind method '" + mdname + "' for instance '" + instance_type + "'.");
+ ERR_FAIL_V_MSG(nullptr, vformat("Couldn't bind method '%s' for instance '%s'.", mdname, instance_type));
}
if (!p_compatibility && type->method_map.has(mdname)) {
memdelete(p_bind);
// overloading not supported
- ERR_FAIL_V_MSG(nullptr, "Method already bound '" + instance_type + "::" + mdname + "'.");
+ ERR_FAIL_V_MSG(nullptr, vformat("Method already bound '%s::%s'.", instance_type, mdname));
}
#ifdef DEBUG_METHODS_ENABLED
if (method_name.args.size() > p_bind->get_argument_count()) {
memdelete(p_bind);
- ERR_FAIL_V_MSG(nullptr, "Method definition provides more arguments than the method actually has '" + instance_type + "::" + mdname + "'.");
+ ERR_FAIL_V_MSG(nullptr, vformat("Method definition provides more arguments than the method actually has '%s::%s'.", instance_type, mdname));
}
p_bind->set_argument_names(method_name.args);
@@ -1966,7 +1966,7 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_
}
void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual, const Vector<String> &p_arg_names, bool p_object_core) {
- ERR_FAIL_COND_MSG(!classes.has(p_class), "Request for nonexistent class '" + p_class + "'.");
+ ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class));
OBJTYPE_WLOCK;
@@ -1981,7 +1981,7 @@ void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_
if (!p_object_core) {
if (p_arg_names.size() != mi.arguments.size()) {
- WARN_PRINT("Mismatch argument name count for virtual method: " + String(p_class) + "::" + p_method.name);
+ WARN_PRINT(vformat("Mismatch argument name count for virtual method: '%s::%s'.", String(p_class), p_method.name));
} else {
List<PropertyInfo>::Iterator itr = mi.arguments.begin();
for (int i = 0; i < p_arg_names.size(); ++itr, ++i) {
@@ -1992,7 +1992,7 @@ void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_
if (classes[p_class].virtual_methods_map.has(p_method.name)) {
// overloading not supported
- ERR_FAIL_MSG("Virtual method already bound '" + String(p_class) + "::" + p_method.name + "'.");
+ ERR_FAIL_MSG(vformat("Virtual method already bound '%s::%s'.", String(p_class), p_method.name));
}
classes[p_class].virtual_methods.push_back(mi);
classes[p_class].virtual_methods_map[p_method.name] = mi;
@@ -2001,7 +2001,7 @@ void ClassDB::add_virtual_method(const StringName &p_class, const MethodInfo &p_
}
void ClassDB::get_virtual_methods(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance) {
- ERR_FAIL_COND_MSG(!classes.has(p_class), "Request for nonexistent class '" + p_class + "'.");
+ ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class));
#ifdef DEBUG_METHODS_ENABLED
@@ -2022,7 +2022,7 @@ void ClassDB::get_virtual_methods(const StringName &p_class, List<MethodInfo> *p
}
void ClassDB::add_extension_class_virtual_method(const StringName &p_class, const GDExtensionClassVirtualMethodInfo *p_method_info) {
- ERR_FAIL_COND_MSG(!classes.has(p_class), "Request for nonexistent class '" + p_class + "'.");
+ ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class));
#ifdef DEBUG_METHODS_ENABLED
PackedStringArray arg_names;
@@ -2046,7 +2046,7 @@ void ClassDB::add_extension_class_virtual_method(const StringName &p_class, cons
void ClassDB::set_class_enabled(const StringName &p_class, bool p_enable) {
OBJTYPE_WLOCK;
- ERR_FAIL_COND_MSG(!classes.has(p_class), "Request for nonexistent class '" + p_class + "'.");
+ ERR_FAIL_COND_MSG(!classes.has(p_class), vformat("Request for nonexistent class '%s'.", p_class));
classes[p_class].disabled = !p_enable;
}
@@ -2060,7 +2060,7 @@ bool ClassDB::is_class_enabled(const StringName &p_class) {
}
}
- ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, false, vformat("Cannot get class '%s'.", String(p_class)));
return !ti->disabled;
}
@@ -2068,7 +2068,7 @@ bool ClassDB::is_class_exposed(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) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, false, vformat("Cannot get class '%s'.", String(p_class)));
return ti->exposed;
}
@@ -2076,7 +2076,7 @@ bool ClassDB::is_class_reloadable(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) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, false, vformat("Cannot get class '%s'.", String(p_class)));
return ti->reloadable;
}
@@ -2084,7 +2084,7 @@ 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) + "'.");
+ ERR_FAIL_NULL_V_MSG(ti, false, vformat("Cannot get class '%s'.", String(p_class)));
return ti->is_runtime;
}
@@ -2194,14 +2194,14 @@ Variant ClassDB::class_get_default_property_value(const StringName &p_class, con
void ClassDB::register_extension_class(ObjectGDExtension *p_extension) {
GLOBAL_LOCK_FUNCTION;
- ERR_FAIL_COND_MSG(classes.has(p_extension->class_name), "Class already registered: " + String(p_extension->class_name));
- ERR_FAIL_COND_MSG(!classes.has(p_extension->parent_class_name), "Parent class name for extension class not found: " + String(p_extension->parent_class_name));
+ ERR_FAIL_COND_MSG(classes.has(p_extension->class_name), vformat("Class already registered: '%s'.", String(p_extension->class_name)));
+ ERR_FAIL_COND_MSG(!classes.has(p_extension->parent_class_name), vformat("Parent class name for extension class not found: '%s'.", String(p_extension->parent_class_name)));
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");
+ ERR_FAIL_COND_MSG(p_extension->is_runtime && parent->gdextension && !parent->is_runtime, vformat("Extension runtime class '%s' cannot descend from '%s' which isn't also a runtime class.", String(p_extension->class_name), parent->name));
#endif
ClassInfo c;
@@ -2217,7 +2217,7 @@ void ClassDB::register_extension_class(ObjectGDExtension *p_extension) {
concrete_ancestor->gdextension != nullptr) {
concrete_ancestor = concrete_ancestor->inherits_ptr;
}
- ERR_FAIL_NULL_MSG(concrete_ancestor->creation_func, "Extension class " + String(p_extension->class_name) + " cannot extend native abstract class " + String(concrete_ancestor->name));
+ ERR_FAIL_NULL_MSG(concrete_ancestor->creation_func, vformat("Extension class '%s' cannot extend native abstract class '%s'.", String(p_extension->class_name), String(concrete_ancestor->name)));
c.creation_func = concrete_ancestor->creation_func;
}
c.inherits = parent->name;
@@ -2241,7 +2241,7 @@ void ClassDB::register_extension_class(ObjectGDExtension *p_extension) {
void ClassDB::unregister_extension_class(const StringName &p_class, bool p_free_method_binds) {
ClassInfo *c = classes.getptr(p_class);
- ERR_FAIL_NULL_MSG(c, "Class '" + String(p_class) + "' does not exist.");
+ ERR_FAIL_NULL_MSG(c, vformat("Class '%s' does not exist.", String(p_class)));
if (p_free_method_binds) {
for (KeyValue<StringName, MethodBind *> &F : c->method_map) {
memdelete(F.value);
diff --git a/core/object/object.cpp b/core/object/object.cpp
index 6c7cf1599a..e88b3e70d6 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -752,7 +752,7 @@ Variant Object::callv(const StringName &p_method, const Array &p_args) {
Callable::CallError ce;
const Variant ret = callp(p_method, argptrs, p_args.size(), ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(Variant(), "Error calling method from 'callv': " + Variant::get_call_error_text(this, p_method, argptrs, p_args.size(), ce) + ".");
+ ERR_FAIL_V_MSG(Variant(), vformat("Error calling method from 'callv': %s.", Variant::get_call_error_text(this, p_method, argptrs, p_args.size(), ce)));
}
return ret;
}
@@ -1001,7 +1001,7 @@ void Object::set_meta(const StringName &p_name, const Variant &p_value) {
if (E) {
E->value = p_value;
} else {
- ERR_FAIL_COND_MSG(!p_name.operator String().is_valid_ascii_identifier(), "Invalid metadata identifier: '" + p_name + "'.");
+ ERR_FAIL_COND_MSG(!p_name.operator String().is_valid_ascii_identifier(), vformat("Invalid metadata identifier: '%s'.", p_name));
Variant *V = &metadata.insert(p_name, p_value)->value;
const String &sname = p_name;
@@ -1017,7 +1017,7 @@ Variant Object::get_meta(const StringName &p_name, const Variant &p_default) con
if (p_default != Variant()) {
return p_default;
} else {
- ERR_FAIL_V_MSG(Variant(), "The object does not have any 'meta' values with the key '" + p_name + "'.");
+ ERR_FAIL_V_MSG(Variant(), vformat("The object does not have any 'meta' values with the key '%s'.", p_name));
}
}
return metadata[p_name];
@@ -1073,8 +1073,8 @@ void Object::get_meta_list(List<StringName> *p_list) const {
void Object::add_user_signal(const MethodInfo &p_signal) {
ERR_FAIL_COND_MSG(p_signal.name.is_empty(), "Signal name cannot be empty.");
- ERR_FAIL_COND_MSG(ClassDB::has_signal(get_class_name(), p_signal.name), "User signal's name conflicts with a built-in signal of '" + get_class_name() + "'.");
- ERR_FAIL_COND_MSG(signal_map.has(p_signal.name), "Trying to add already existing signal '" + p_signal.name + "'.");
+ ERR_FAIL_COND_MSG(ClassDB::has_signal(get_class_name(), p_signal.name), vformat("User signal's name conflicts with a built-in signal of '%s'.", get_class_name()));
+ ERR_FAIL_COND_MSG(signal_map.has(p_signal.name), vformat("Trying to add already existing signal '%s'.", p_signal.name));
SignalData s;
s.user = p_signal;
signal_map[p_signal.name] = s;
@@ -1139,7 +1139,7 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
#ifdef DEBUG_ENABLED
bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_name);
//check in script
- ERR_FAIL_COND_V_MSG(!signal_is_valid && !script.is_null() && !Ref<Script>(script)->has_script_signal(p_name), ERR_UNAVAILABLE, "Can't emit non-existing signal " + String("\"") + p_name + "\".");
+ ERR_FAIL_COND_V_MSG(!signal_is_valid && !script.is_null() && !Ref<Script>(script)->has_script_signal(p_name), ERR_UNAVAILABLE, vformat("Can't emit non-existing signal \"%s\".", p_name));
#endif
//not connected? just return
return ERR_UNAVAILABLE;
@@ -1212,7 +1212,7 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
if (ce.error == Callable::CallError::CALL_ERROR_INVALID_METHOD && target && !ClassDB::class_exists(target->get_class_name())) {
//most likely object is not initialized yet, do not throw error.
} else {
- ERR_PRINT("Error calling from signal '" + String(p_name) + "' to callable: " + Variant::get_callable_error_text(callable, args, argc, ce) + ".");
+ ERR_PRINT(vformat("Error calling from signal '%s' to callable: %s.", String(p_name), Variant::get_callable_error_text(callable, args, argc, ce)));
err = ERR_METHOD_NOT_FOUND;
}
}
@@ -1373,15 +1373,15 @@ void Object::get_signals_connected_to_this(List<Connection> *p_connections) cons
}
Error Object::connect(const StringName &p_signal, const Callable &p_callable, uint32_t p_flags) {
- ERR_FAIL_COND_V_MSG(p_callable.is_null(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "': the provided callable is null.");
+ ERR_FAIL_COND_V_MSG(p_callable.is_null(), ERR_INVALID_PARAMETER, vformat("Cannot connect to '%s': the provided callable is null.", p_signal));
if (p_callable.is_standard()) {
// FIXME: This branch should probably removed in favor of the `is_valid()` branch, but there exist some classes
// that call `connect()` before they are fully registered with ClassDB. Until all such classes can be found
// and registered soon enough this branch is needed to allow `connect()` to succeed.
- ERR_FAIL_NULL_V_MSG(p_callable.get_object(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "' to callable '" + p_callable + "': the callable object is null.");
+ ERR_FAIL_NULL_V_MSG(p_callable.get_object(), ERR_INVALID_PARAMETER, vformat("Cannot connect to '%s' to callable '%s': the callable object is null.", p_signal, p_callable));
} else {
- ERR_FAIL_COND_V_MSG(!p_callable.is_valid(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "': the provided callable is not valid: " + p_callable);
+ ERR_FAIL_COND_V_MSG(!p_callable.is_valid(), ERR_INVALID_PARAMETER, vformat("Cannot connect to '%s': the provided callable is not valid: '%s'.", p_signal, p_callable));
}
SignalData *s = signal_map.getptr(p_signal);
@@ -1402,7 +1402,7 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui
#endif
}
- ERR_FAIL_COND_V_MSG(!signal_is_valid, ERR_INVALID_PARAMETER, "In Object of type '" + String(get_class()) + "': Attempt to connect nonexistent signal '" + p_signal + "' to callable '" + p_callable + "'.");
+ ERR_FAIL_COND_V_MSG(!signal_is_valid, ERR_INVALID_PARAMETER, vformat("In Object of type '%s': Attempt to connect nonexistent signal '%s' to callable '%s'.", String(get_class()), p_signal, p_callable));
signal_map[p_signal] = SignalData();
s = &signal_map[p_signal];
@@ -1414,7 +1414,7 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui
s->slot_map[*p_callable.get_base_comparator()].reference_count++;
return OK;
} else {
- ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Signal '" + p_signal + "' is already connected to given callable '" + p_callable + "' in that object.");
+ ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, vformat("Signal '%s' is already connected to given callable '%s' in that object.", p_signal, p_callable));
}
}
@@ -1441,7 +1441,7 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui
}
bool Object::is_connected(const StringName &p_signal, const Callable &p_callable) const {
- ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, "Cannot determine if connected to '" + p_signal + "': the provided callable is null."); // Should use `is_null`, see note in `connect` about the use of `is_valid`.
+ ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, vformat("Cannot determine if connected to '%s': the provided callable is null.", p_signal)); // Should use `is_null`, see note in `connect` about the use of `is_valid`.
const SignalData *s = signal_map.getptr(p_signal);
if (!s) {
bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal);
@@ -1453,7 +1453,7 @@ bool Object::is_connected(const StringName &p_signal, const Callable &p_callable
return false;
}
- ERR_FAIL_V_MSG(false, "Nonexistent signal: " + p_signal + ".");
+ ERR_FAIL_V_MSG(false, vformat("Nonexistent signal: '%s'.", p_signal));
}
return s->slot_map.has(*p_callable.get_base_comparator());
@@ -1471,7 +1471,7 @@ bool Object::has_connections(const StringName &p_signal) const {
return false;
}
- ERR_FAIL_V_MSG(false, "Nonexistent signal: " + p_signal + ".");
+ ERR_FAIL_V_MSG(false, vformat("Nonexistent signal: '%s'.", p_signal));
}
return !s->slot_map.is_empty();
@@ -1482,17 +1482,17 @@ void Object::disconnect(const StringName &p_signal, const Callable &p_callable)
}
bool Object::_disconnect(const StringName &p_signal, const Callable &p_callable, bool p_force) {
- ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, "Cannot disconnect from '" + p_signal + "': the provided callable is null."); // Should use `is_null`, see note in `connect` about the use of `is_valid`.
+ ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, vformat("Cannot disconnect from '%s': the provided callable is null.", p_signal)); // Should use `is_null`, see note in `connect` about the use of `is_valid`.
SignalData *s = signal_map.getptr(p_signal);
if (!s) {
bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal) ||
(!script.is_null() && Ref<Script>(script)->has_script_signal(p_signal));
- ERR_FAIL_COND_V_MSG(signal_is_valid, false, "Attempt to disconnect a nonexistent connection from '" + to_string() + "'. Signal: '" + p_signal + "', callable: '" + p_callable + "'.");
+ ERR_FAIL_COND_V_MSG(signal_is_valid, false, vformat("Attempt to disconnect a nonexistent connection from '%s'. Signal: '%s', callable: '%s'.", to_string(), p_signal, p_callable));
}
- ERR_FAIL_NULL_V_MSG(s, false, vformat("Disconnecting nonexistent signal '%s' in %s.", p_signal, to_string()));
+ ERR_FAIL_NULL_V_MSG(s, false, vformat("Disconnecting nonexistent signal '%s' in '%s'.", p_signal, to_string()));
- ERR_FAIL_COND_V_MSG(!s->slot_map.has(*p_callable.get_base_comparator()), false, "Attempt to disconnect a nonexistent connection from '" + to_string() + "'. Signal: '" + p_signal + "', callable: '" + p_callable + "'.");
+ ERR_FAIL_COND_V_MSG(!s->slot_map.has(*p_callable.get_base_comparator()), false, vformat("Attempt to disconnect a nonexistent connection from '%s'. Signal: '%s', callable: '%s'.", to_string(), p_signal, p_callable));
SignalData::Slot *slot = &s->slot_map[*p_callable.get_base_comparator()];
@@ -2130,7 +2130,7 @@ Object::~Object() {
if (_emitting) {
//@todo this may need to actually reach the debugger prioritarily somehow because it may crash before
- ERR_PRINT("Object " + to_string() + " was freed or unreferenced while a signal is being emitted from it. Try connecting to the signal using 'CONNECT_DEFERRED' flag, or use queue_free() to free the object (if this object is a Node) to avoid this error and potential crashes.");
+ ERR_PRINT(vformat("Object '%s' was freed or unreferenced while a signal is being emitted from it. Try connecting to the signal using 'CONNECT_DEFERRED' flag, or use queue_free() to free the object (if this object is a Node) to avoid this error and potential crashes.", to_string()));
}
// Drop all connections to the signals of this object.
diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp
index 8571ccff43..15fa7e13f8 100644
--- a/core/object/script_language.cpp
+++ b/core/object/script_language.cpp
@@ -241,9 +241,9 @@ Error ScriptServer::register_language(ScriptLanguage *p_language) {
ERR_FAIL_COND_V_MSG(_language_count >= MAX_LANGUAGES, ERR_UNAVAILABLE, "Script languages limit has been reach, cannot register more.");
for (int i = 0; i < _language_count; i++) {
const ScriptLanguage *other_language = _languages[i];
- ERR_FAIL_COND_V_MSG(other_language->get_extension() == p_language->get_extension(), ERR_ALREADY_EXISTS, "A script language with extension '" + p_language->get_extension() + "' is already registered.");
- ERR_FAIL_COND_V_MSG(other_language->get_name() == p_language->get_name(), ERR_ALREADY_EXISTS, "A script language with name '" + p_language->get_name() + "' is already registered.");
- ERR_FAIL_COND_V_MSG(other_language->get_type() == p_language->get_type(), ERR_ALREADY_EXISTS, "A script language with type '" + p_language->get_type() + "' is already registered.");
+ ERR_FAIL_COND_V_MSG(other_language->get_extension() == p_language->get_extension(), ERR_ALREADY_EXISTS, vformat("A script language with extension '%s' is already registered.", p_language->get_extension()));
+ ERR_FAIL_COND_V_MSG(other_language->get_name() == p_language->get_name(), ERR_ALREADY_EXISTS, vformat("A script language with name '%s' is already registered.", p_language->get_name()));
+ ERR_FAIL_COND_V_MSG(other_language->get_type() == p_language->get_type(), ERR_ALREADY_EXISTS, vformat("A script language with type '%s' is already registered.", p_language->get_type()));
}
_languages[_language_count++] = p_language;
return OK;
diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp
index 6b80af3f28..db6c02f330 100644
--- a/core/object/undo_redo.cpp
+++ b/core/object/undo_redo.cpp
@@ -366,7 +366,7 @@ void UndoRedo::_process_operation_list(List<Operation>::Element *E, bool p_execu
Variant ret;
op.callable.callp(nullptr, 0, ret, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT("Error calling UndoRedo method operation '" + String(op.name) + "': " + Variant::get_call_error_text(obj, op.name, nullptr, 0, ce));
+ ERR_PRINT(vformat("Error calling UndoRedo method operation '%s': %s.", String(op.name), Variant::get_call_error_text(obj, op.name, nullptr, 0, ce)));
}
#ifdef TOOLS_ENABLED
Resource *res = Object::cast_to<Resource>(obj);
diff --git a/core/os/os.cpp b/core/os/os.cpp
index 91a9f4a2fd..b41ea0a87f 100644
--- a/core/os/os.cpp
+++ b/core/os/os.cpp
@@ -354,7 +354,7 @@ void OS::ensure_user_data_dir() {
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
Error err = da->make_dir_recursive(dd);
- ERR_FAIL_COND_MSG(err != OK, "Error attempting to create data dir: " + dd + ".");
+ ERR_FAIL_COND_MSG(err != OK, vformat("Error attempting to create data dir: %s.", dd));
}
String OS::get_model_name() const {
diff --git a/core/string/node_path.cpp b/core/string/node_path.cpp
index 55624c4db8..9b5d3bd1fa 100644
--- a/core/string/node_path.cpp
+++ b/core/string/node_path.cpp
@@ -422,7 +422,7 @@ NodePath::NodePath(const String &p_path) {
continue; // Allow end-of-path :
}
- ERR_FAIL_MSG("Invalid NodePath '" + p_path + "'.");
+ ERR_FAIL_MSG(vformat("Invalid NodePath '%s'.", p_path));
}
subpath.push_back(str);
diff --git a/core/string/translation_po.cpp b/core/string/translation_po.cpp
index 756b5af673..5aa230ff00 100644
--- a/core/string/translation_po.cpp
+++ b/core/string/translation_po.cpp
@@ -248,7 +248,7 @@ void TranslationPO::add_message(const StringName &p_src_text, const StringName &
HashMap<StringName, Vector<StringName>> &map_id_str = translation_map[p_context];
if (map_id_str.has(p_src_text)) {
- WARN_PRINT("Double translations for \"" + String(p_src_text) + "\" under the same context \"" + String(p_context) + "\" for locale \"" + get_locale() + "\".\nThere should only be one unique translation for a given string under the same context.");
+ WARN_PRINT(vformat("Double translations for \"%s\" under the same context \"%s\" for locale \"%s\".\nThere should only be one unique translation for a given string under the same context.", String(p_src_text), String(p_context), get_locale()));
map_id_str[p_src_text].set(0, p_xlated_text);
} else {
map_id_str[p_src_text].push_back(p_xlated_text);
@@ -256,12 +256,12 @@ void TranslationPO::add_message(const StringName &p_src_text, const StringName &
}
void TranslationPO::add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context) {
- ERR_FAIL_COND_MSG(p_plural_xlated_texts.size() != plural_forms, "Trying to add plural texts that don't match the required number of plural forms for locale \"" + get_locale() + "\"");
+ ERR_FAIL_COND_MSG(p_plural_xlated_texts.size() != plural_forms, vformat("Trying to add plural texts that don't match the required number of plural forms for locale \"%s\".", get_locale()));
HashMap<StringName, Vector<StringName>> &map_id_str = translation_map[p_context];
if (map_id_str.has(p_src_text)) {
- WARN_PRINT("Double translations for \"" + p_src_text + "\" under the same context \"" + p_context + "\" for locale " + get_locale() + ".\nThere should only be one unique translation for a given string under the same context.");
+ WARN_PRINT(vformat("Double translations for \"%s\" under the same context \"%s\" for locale %s.\nThere should only be one unique translation for a given string under the same context.", p_src_text, p_context, get_locale()));
map_id_str[p_src_text].clear();
}
@@ -282,7 +282,7 @@ StringName TranslationPO::get_message(const StringName &p_src_text, const String
if (!translation_map.has(p_context) || !translation_map[p_context].has(p_src_text)) {
return StringName();
}
- ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), "Source text \"" + String(p_src_text) + "\" is registered but doesn't have a translation. Please report this bug.");
+ ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), vformat("Source text \"%s\" is registered but doesn't have a translation. Please report this bug.", String(p_src_text)));
return translation_map[p_context][p_src_text][0];
}
@@ -298,7 +298,7 @@ StringName TranslationPO::get_plural_message(const StringName &p_src_text, const
if (!translation_map.has(p_context) || !translation_map[p_context].has(p_src_text)) {
return StringName();
}
- ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), "Source text \"" + String(p_src_text) + "\" is registered but doesn't have a translation. Please report this bug.");
+ ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), vformat("Source text \"%s\" is registered but doesn't have a translation. Please report this bug.", String(p_src_text)));
int plural_index = _get_plural_index(p_n);
ERR_FAIL_COND_V_MSG(plural_index < 0 || translation_map[p_context][p_src_text].size() < plural_index + 1, StringName(), "Plural index returned or number of plural translations is not valid. Please report this bug.");
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 38f610eaca..9bfc0a67cb 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -1821,7 +1821,7 @@ String String::num(double p_num, int p_decimals) {
#endif
buf[324] = 0;
- //destroy trailing zeroes
+ // Destroy trailing zeroes, except one after period.
{
bool period = false;
int z = 0;
@@ -1838,7 +1838,7 @@ String String::num(double p_num, int p_decimals) {
if (buf[z] == '0') {
buf[z] = 0;
} else if (buf[z] == '.') {
- buf[z] = 0;
+ buf[z + 1] = '0';
break;
} else {
break;
@@ -1931,14 +1931,28 @@ String String::num_real(double p_num, bool p_trailing) {
return num_int64((int64_t)p_num);
}
}
-#ifdef REAL_T_IS_DOUBLE
int decimals = 14;
-#else
+ // We want to align the digits to the above sane default, so we only need
+ // to subtract log10 for numbers with a positive power of ten magnitude.
+ const double abs_num = Math::abs(p_num);
+ if (abs_num > 10) {
+ decimals -= (int)floor(log10(abs_num));
+ }
+ return num(p_num, decimals);
+}
+
+String String::num_real(float p_num, bool p_trailing) {
+ if (p_num == (float)(int64_t)p_num) {
+ if (p_trailing) {
+ return num_int64((int64_t)p_num) + ".0";
+ } else {
+ return num_int64((int64_t)p_num);
+ }
+ }
int decimals = 6;
-#endif
// We want to align the digits to the above sane default, so we only need
// to subtract log10 for numbers with a positive power of ten magnitude.
- double abs_num = Math::abs(p_num);
+ const float abs_num = Math::abs(p_num);
if (abs_num > 10) {
decimals -= (int)floor(log10(abs_num));
}
@@ -4618,7 +4632,7 @@ String String::humanize_size(uint64_t p_size) {
}
if (magnitude == 0) {
- return String::num(p_size) + " " + RTR("B");
+ return String::num_uint64(p_size) + " " + RTR("B");
} else {
String suffix;
switch (magnitude) {
diff --git a/core/string/ustring.h b/core/string/ustring.h
index d6c0907ecc..11be148084 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -334,6 +334,7 @@ public:
static String num(double p_num, int p_decimals = -1);
static String num_scientific(double p_num);
static String num_real(double p_num, bool p_trailing = true);
+ static String num_real(float p_num, bool p_trailing = true);
static String num_int64(int64_t p_num, int base = 10, bool capitalize_hex = false);
static String num_uint64(uint64_t p_num, int base = 10, bool capitalize_hex = false);
static String chr(char32_t p_char);
diff --git a/core/variant/array.cpp b/core/variant/array.cpp
index d5cc1df599..cf24924e0b 100644
--- a/core/variant/array.cpp
+++ b/core/variant/array.cpp
@@ -387,7 +387,7 @@ int Array::find_custom(const Callable &p_callable, int p_from) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, res, ce);
if (unlikely(ce.error != Callable::CallError::CALL_OK)) {
- ERR_FAIL_V_MSG(ret, "Error calling method from 'find_custom': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(ret, vformat("Error calling method from 'find_custom': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
ERR_FAIL_COND_V_MSG(res.get_type() != Variant::Type::BOOL, ret, "Error on method from 'find_custom': Return type of callable must be boolean.");
@@ -447,7 +447,7 @@ int Array::rfind_custom(const Callable &p_callable, int p_from) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, res, ce);
if (unlikely(ce.error != Callable::CallError::CALL_OK)) {
- ERR_FAIL_V_MSG(-1, "Error calling method from 'rfind_custom': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(-1, vformat("Error calling method from 'rfind_custom': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
ERR_FAIL_COND_V_MSG(res.get_type() != Variant::Type::BOOL, -1, "Error on method from 'rfind_custom': Return type of callable must be boolean.");
@@ -576,7 +576,7 @@ Array Array::filter(const Callable &p_callable) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(Array(), "Error calling method from 'filter': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(Array(), vformat("Error calling method from 'filter': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
if (result.operator bool()) {
@@ -602,7 +602,7 @@ Array Array::map(const Callable &p_callable) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(Array(), "Error calling method from 'map': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(Array(), vformat("Error calling method from 'map': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
new_arr[i] = result;
@@ -628,7 +628,7 @@ Variant Array::reduce(const Callable &p_callable, const Variant &p_accum) const
Callable::CallError ce;
p_callable.callp(argptrs, 2, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(Variant(), "Error calling method from 'reduce': " + Variant::get_callable_error_text(p_callable, argptrs, 2, ce));
+ ERR_FAIL_V_MSG(Variant(), vformat("Error calling method from 'reduce': %s.", Variant::get_callable_error_text(p_callable, argptrs, 2, ce)));
}
ret = result;
}
@@ -645,7 +645,7 @@ bool Array::any(const Callable &p_callable) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(false, "Error calling method from 'any': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(false, vformat("Error calling method from 'any': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
if (result.operator bool()) {
@@ -667,7 +667,7 @@ bool Array::all(const Callable &p_callable) const {
Callable::CallError ce;
p_callable.callp(argptrs, 1, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
- ERR_FAIL_V_MSG(false, "Error calling method from 'all': " + Variant::get_callable_error_text(p_callable, argptrs, 1, ce));
+ ERR_FAIL_V_MSG(false, vformat("Error calling method from 'all': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
if (!(result.operator bool())) {
diff --git a/core/variant/container_type_validate.h b/core/variant/container_type_validate.h
index 1bda6f3127..867aadc5d4 100644
--- a/core/variant/container_type_validate.h
+++ b/core/variant/container_type_validate.h
@@ -96,7 +96,7 @@ struct ContainerTypeValidate {
return true;
}
- ERR_FAIL_V_MSG(false, "Attempted to " + String(p_operation) + " a variable of type '" + Variant::get_type_name(inout_variant.get_type()) + "' into a " + where + " of type '" + Variant::get_type_name(type) + "'.");
+ ERR_FAIL_V_MSG(false, vformat("Attempted to %s a variable of type '%s' into a %s of type '%s'.", String(p_operation), Variant::get_type_name(inout_variant.get_type()), where, Variant::get_type_name(type)));
}
if (type != Variant::OBJECT) {
@@ -115,7 +115,7 @@ struct ContainerTypeValidate {
return true; // This is fine, it's null.
}
Object *object = ObjectDB::get_instance(object_id);
- ERR_FAIL_NULL_V_MSG(object, false, "Attempted to " + String(p_operation) + " an invalid (previously freed?) object instance into a '" + String(where) + ".");
+ ERR_FAIL_NULL_V_MSG(object, false, vformat("Attempted to %s an invalid (previously freed?) object instance into a '%s'.", String(p_operation), String(where)));
#else
Object *object = p_variant;
if (object == nullptr) {
@@ -128,7 +128,7 @@ struct ContainerTypeValidate {
StringName obj_class = object->get_class_name();
if (obj_class != class_name) {
- ERR_FAIL_COND_V_MSG(!ClassDB::is_parent_class(object->get_class_name(), class_name), false, "Attempted to " + String(p_operation) + " an object of type '" + object->get_class() + "' into a " + where + ", which does not inherit from '" + String(class_name) + "'.");
+ ERR_FAIL_COND_V_MSG(!ClassDB::is_parent_class(object->get_class_name(), class_name), false, vformat("Attempted to %s an object of type '%s' into a %s, which does not inherit from '%s'.", String(p_operation), object->get_class(), where, String(class_name)));
}
if (script.is_null()) {
@@ -138,8 +138,8 @@ struct ContainerTypeValidate {
Ref<Script> other_script = object->get_script();
// Check base script..
- ERR_FAIL_COND_V_MSG(other_script.is_null(), false, "Attempted to " + String(p_operation) + " an object into a " + String(where) + ", that does not inherit from '" + String(script->get_class_name()) + "'.");
- ERR_FAIL_COND_V_MSG(!other_script->inherits_script(script), false, "Attempted to " + String(p_operation) + " an object into a " + String(where) + ", that does not inherit from '" + String(script->get_class_name()) + "'.");
+ ERR_FAIL_COND_V_MSG(other_script.is_null(), false, vformat("Attempted to %s an object into a %s, that does not inherit from '%s'.", String(p_operation), String(where), String(script->get_class_name())));
+ ERR_FAIL_COND_V_MSG(!other_script->inherits_script(script), false, vformat("Attempted to %s an object into a %s, that does not inherit from '%s'.", String(p_operation), String(where), String(script->get_class_name())));
return true;
}
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index 49119b9303..26c0e8b6e1 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -1738,7 +1738,7 @@ String Variant::stringify(int recursion_count) const {
case INT:
return itos(_data._int);
case FLOAT:
- return rtos(_data._float);
+ return String::num_real(_data._float, true);
case STRING:
return *reinterpret_cast<const String *>(_data._mem);
case VECTOR2:
diff --git a/core/variant/variant_construct.cpp b/core/variant/variant_construct.cpp
index 0cbdebb110..5d684c96a2 100644
--- a/core/variant/variant_construct.cpp
+++ b/core/variant/variant_construct.cpp
@@ -45,7 +45,7 @@ static LocalVector<VariantConstructData> construct_data[Variant::VARIANT_MAX];
template <typename T>
static void add_constructor(const Vector<String> &arg_names) {
- ERR_FAIL_COND_MSG(arg_names.size() != T::get_argument_count(), "Argument names size mismatch for " + Variant::get_type_name(T::get_base_type()) + ".");
+ ERR_FAIL_COND_MSG(arg_names.size() != T::get_argument_count(), vformat("Argument names size mismatch for '%s'.", Variant::get_type_name(T::get_base_type())));
VariantConstructData cd;
cd.construct = T::construct;
diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp
index 490ccd04a6..8e4231c90e 100644
--- a/core/variant/variant_utility.cpp
+++ b/core/variant/variant_utility.cpp
@@ -1673,7 +1673,7 @@ static void register_utility_function(const String &p_name, const Vector<String>
bfi.argnames = argnames;
bfi.argcount = T::get_argument_count();
if (!bfi.is_vararg) {
- ERR_FAIL_COND_MSG(argnames.size() != bfi.argcount, "wrong number of arguments binding utility function: " + name);
+ ERR_FAIL_COND_MSG(argnames.size() != bfi.argcount, vformat("Wrong number of arguments binding utility function: '%s'.", name));
}
bfi.get_arg_type = T::get_argument_type;
bfi.return_type = T::get_return_type();
diff --git a/doc/classes/ClassDB.xml b/doc/classes/ClassDB.xml
index d1b666097c..d01a308e32 100644
--- a/doc/classes/ClassDB.xml
+++ b/doc/classes/ClassDB.xml
@@ -16,7 +16,7 @@
Returns [code]true[/code] if objects can be instantiated from the specified [param class], otherwise returns [code]false[/code].
</description>
</method>
- <method name="class_call_static_method" qualifiers="vararg">
+ <method name="class_call_static" qualifiers="vararg">
<return type="Variant" />
<param index="0" name="class" type="StringName" />
<param index="1" name="method" type="StringName" />
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 4b679a10d9..82b89beed1 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -139,11 +139,12 @@
<description>
Displays OS native dialog for selecting files or directories in the file system.
Each filter string in the [param filters] array should be formatted like this: [code]*.txt,*.doc;Text Files[/code]. The description text of the filter is optional and can be omitted. See also [member FileDialog.filters].
- Callbacks have the following arguments: [code]status: bool, selected_paths: PackedStringArray, selected_filter_index: int[/code].
- [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG_FILE] feature. Supported platforms include Linux (X11/Wayland), Windows, and macOS.
+ Callbacks have the following arguments: [code]status: bool, selected_paths: PackedStringArray, selected_filter_index: int[/code]. [b]On Android,[/b] callback argument [code]selected_filter_index[/code] is always zero.
+ [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG_FILE] feature. Supported platforms include Linux (X11/Wayland), Windows, macOS, and Android.
[b]Note:[/b] [param current_directory] might be ignored.
- [b]Note:[/b] On Linux, [param show_hidden] is ignored.
- [b]Note:[/b] On macOS, native file dialogs have no title.
+ [b]Note:[/b] On Android, the filter strings in the [param filters] array should be specified using MIME types, for example:[code]image/png, image/jpeg"[/code]. Additionally, the [param mode] [constant FILE_DIALOG_MODE_OPEN_ANY] is not supported on Android.
+ [b]Note:[/b] On Android and Linux, [param show_hidden] is ignored.
+ [b]Note:[/b] On Android and macOS, native file dialogs have no title.
[b]Note:[/b] On macOS, sandboxed apps will save security-scoped bookmarks to retain access to the opened folders across multiple sessions. Use [method OS.get_granted_permissions] to get a list of saved bookmarks.
</description>
</method>
@@ -166,7 +167,7 @@
- [code]"values"[/code] - [PackedStringArray] of values. If empty, boolean option (check box) is used.
- [code]"default"[/code] - default selected option index ([int]) or default boolean value ([bool]).
Callbacks have the following arguments: [code]status: bool, selected_paths: PackedStringArray, selected_filter_index: int, selected_option: Dictionary[/code].
- [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG_FILE] feature. Supported platforms include Linux (X11/Wayland), Windows, and macOS.
+ [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG_FILE_EXTRA] feature. Supported platforms include Linux (X11/Wayland), Windows, and macOS.
[b]Note:[/b] [param current_directory] might be ignored.
[b]Note:[/b] On Linux (X11), [param show_hidden] is ignored.
[b]Note:[/b] On macOS, native file dialogs have no title.
@@ -1889,7 +1890,10 @@
Display server supports spawning text input dialogs using the operating system's native look-and-feel. See [method dialog_input_text]. [b]Windows, macOS[/b]
</constant>
<constant name="FEATURE_NATIVE_DIALOG_FILE" value="25" enum="Feature">
- Display server supports spawning dialogs for selecting files or directories using the operating system's native look-and-feel. See [method file_dialog_show] and [method file_dialog_with_options_show]. [b]Windows, macOS, Linux (X11/Wayland)[/b]
+ Display server supports spawning dialogs for selecting files or directories using the operating system's native look-and-feel. See [method file_dialog_show]. [b]Windows, macOS, Linux (X11/Wayland), Android[/b]
+ </constant>
+ <constant name="FEATURE_NATIVE_DIALOG_FILE_EXTRA" value="26" enum="Feature">
+ The display server supports all features of [constant FEATURE_NATIVE_DIALOG_FILE], with the added functionality of Options and native dialog file access to [code]res://[/code] and [code]user://[/code] paths. See [method file_dialog_show] and [method file_dialog_with_options_show]. [b]Windows, macOS, Linux (X11/Wayland)[/b]
</constant>
<constant name="MOUSE_MODE_VISIBLE" value="0" enum="MouseMode">
Makes the mouse cursor visible if it is hidden.
diff --git a/doc/classes/EditorFeatureProfile.xml b/doc/classes/EditorFeatureProfile.xml
index 24e02b7ee5..b823a4bd14 100644
--- a/doc/classes/EditorFeatureProfile.xml
+++ b/doc/classes/EditorFeatureProfile.xml
@@ -121,7 +121,10 @@
<constant name="FEATURE_HISTORY_DOCK" value="7" enum="Feature">
The History dock. If this feature is disabled, the History dock won't be visible.
</constant>
- <constant name="FEATURE_MAX" value="8" enum="Feature">
+ <constant name="FEATURE_GAME" value="8" enum="Feature">
+ The Game tab, which allows embedding the game window and selecting nodes by clicking inside of it. If this feature is disabled, the Game tab won't display.
+ </constant>
+ <constant name="FEATURE_MAX" value="9" enum="Feature">
Represents the size of the [enum Feature] enum.
</constant>
</constants>
diff --git a/doc/classes/FileDialog.xml b/doc/classes/FileDialog.xml
index 18b8eb1d39..64369bec30 100644
--- a/doc/classes/FileDialog.xml
+++ b/doc/classes/FileDialog.xml
@@ -146,6 +146,7 @@
</member>
<member name="filters" type="PackedStringArray" setter="set_filters" getter="get_filters" default="PackedStringArray()">
The available file type filters. Each filter string in the array should be formatted like this: [code]*.txt,*.doc;Text Files[/code]. The description text of the filter is optional and can be omitted.
+ [b]Note:[/b] For android native dialog, MIME types are used like this: [code]image/*, application/pdf[/code].
</member>
<member name="mode_overrides_title" type="bool" setter="set_mode_overrides_title" getter="is_mode_overriding_title" default="true">
If [code]true[/code], changing the [member file_mode] property will set the window title accordingly (e.g. setting [member file_mode] to [constant FILE_MODE_OPEN_FILE] will change the window title to "Open a File").
@@ -159,12 +160,13 @@
</member>
<member name="show_hidden_files" type="bool" setter="set_show_hidden_files" getter="is_showing_hidden_files" default="false">
If [code]true[/code], the dialog will show hidden files.
- [b]Note:[/b] This property is ignored by native file dialogs on Linux.
+ [b]Note:[/b] This property is ignored by native file dialogs on Android and Linux.
</member>
<member name="size" type="Vector2i" setter="set_size" getter="get_size" overrides="Window" default="Vector2i(640, 360)" />
<member name="title" type="String" setter="set_title" getter="get_title" overrides="Window" default="&quot;Save a File&quot;" />
<member name="use_native_dialog" type="bool" setter="set_use_native_dialog" getter="get_use_native_dialog" default="false">
If [code]true[/code], and if supported by the current [DisplayServer], OS native dialog will be used instead of custom one.
+ [b]Note:[/b] On Android, it is only supported when using [constant ACCESS_FILESYSTEM]. For access mode [constant ACCESS_RESOURCES] and [constant ACCESS_USERDATA], the system will fall back to custom FileDialog.
[b]Note:[/b] On Linux and macOS, sandboxed apps always use native dialogs to access the host file system.
[b]Note:[/b] On macOS, sandboxed apps will save security-scoped bookmarks to retain access to the opened folders across multiple sessions. Use [method OS.get_granted_permissions] to get a list of saved bookmarks.
[b]Note:[/b] Native dialogs are isolated from the base process, file dialog properties can't be modified once the dialog is shown.
diff --git a/doc/classes/ImporterMesh.xml b/doc/classes/ImporterMesh.xml
index 28ee5710d9..745d7a3d5d 100644
--- a/doc/classes/ImporterMesh.xml
+++ b/doc/classes/ImporterMesh.xml
@@ -49,7 +49,8 @@
<param index="2" name="bone_transform_array" type="Array" />
<description>
Generates all lods for this ImporterMesh.
- [param normal_merge_angle] and [param normal_split_angle] are in degrees and used in the same way as the importer settings in [code]lods[/code]. As a good default, use 25 and 60 respectively.
+ [param normal_merge_angle] is in degrees and used in the same way as the importer settings in [code]lods[/code].
+ [param normal_split_angle] is not used and only remains for compatibility with older versions of the API.
The number of generated lods can be accessed using [method get_surface_lod_count], and each LOD is available in [method get_surface_lod_size] and [method get_surface_lod_indices].
[param bone_transform_array] is an [Array] which can be either empty or contain [Transform3D]s which, for each of the mesh's bone IDs, will apply mesh skinning when generating the LOD mesh variations. This is usually used to account for discrepancies in scale between the mesh itself and its skinning data.
</description>
diff --git a/drivers/gles3/shader_gles3.cpp b/drivers/gles3/shader_gles3.cpp
index a357ee299a..34db37eb76 100644
--- a/drivers/gles3/shader_gles3.cpp
+++ b/drivers/gles3/shader_gles3.cpp
@@ -170,6 +170,10 @@ void ShaderGLES3::_build_variant_code(StringBuilder &builder, uint32_t p_variant
builder.append("#version 300 es\n");
}
+ if (GLES3::Config::get_singleton()->polyfill_half2float) {
+ builder.append("#define USE_HALF2FLOAT\n");
+ }
+
for (int i = 0; i < specialization_count; i++) {
if (p_specialization & (uint64_t(1) << uint64_t(i))) {
builder.append("#define " + String(specializations[i].name) + "\n");
diff --git a/drivers/gles3/shaders/stdlib_inc.glsl b/drivers/gles3/shaders/stdlib_inc.glsl
index f88c218506..004db9e5d0 100644
--- a/drivers/gles3/shaders/stdlib_inc.glsl
+++ b/drivers/gles3/shaders/stdlib_inc.glsl
@@ -1,25 +1,28 @@
-
// Compatibility renames. These are exposed with the "godot_" prefix
// to work around two distinct Adreno bugs:
// 1. Some Adreno devices expose ES310 functions in ES300 shaders.
// Internally, we must use the "godot_" prefix, but user shaders
// will be mapped automatically.
// 2. Adreno 3XX devices have poor implementations of the other packing
-// functions, so we just use our own everywhere to keep it simple.
+// functions, so we just use our own there to keep it simple.
+#ifdef USE_HALF2FLOAT
// Floating point pack/unpack functions are part of the GLSL ES 300 specification used by web and mobile.
+// It appears to be safe to expose these on mobile, but when running through ANGLE this appears to break.
uint float2half(uint f) {
- uint b = f + uint(0x00001000);
- uint e = (b & uint(0x7F800000)) >> 23;
- uint m = b & uint(0x007FFFFF);
- return (b & uint(0x80000000)) >> uint(16) | uint(e > uint(112)) * ((((e - uint(112)) << uint(10)) & uint(0x7C00)) | m >> uint(13)) | (uint(e < uint(113)) & uint(e > uint(101))) * ((((uint(0x007FF000) + m) >> (uint(125) - e)) + uint(1)) >> uint(1)) | uint(e > uint(143)) * uint(0x7FFF);
+ uint e = f & uint(0x7f800000);
+ if (e <= uint(0x38000000)) {
+ return uint(0);
+ } else {
+ return ((f >> uint(16)) & uint(0x8000)) |
+ (((e - uint(0x38000000)) >> uint(13)) & uint(0x7c00)) |
+ ((f >> uint(13)) & uint(0x03ff));
+ }
}
uint half2float(uint h) {
- uint e = (h & uint(0x7C00)) >> uint(10);
- uint m = (h & uint(0x03FF)) << uint(13);
- uint v = m >> uint(23);
- return (h & uint(0x8000)) << uint(16) | uint(e != uint(0)) * ((e + uint(112)) << uint(23) | m) | (uint(e == uint(0)) & uint(m != uint(0))) * ((v - uint(37)) << uint(23) | ((m << (uint(150) - v)) & uint(0x007FE000)));
+ uint h_e = h & uint(0x7c00);
+ return ((h & uint(0x8000)) << uint(16)) | uint((h_e >> uint(10)) != uint(0)) * (((h_e + uint(0x1c000)) << uint(13)) | ((h & uint(0x03ff)) << uint(13)));
}
uint godot_packHalf2x16(vec2 v) {
@@ -50,6 +53,17 @@ vec2 godot_unpackSnorm2x16(uint p) {
return clamp((v - 32767.0) * vec2(0.00003051851), vec2(-1.0), vec2(1.0));
}
+#define packHalf2x16 godot_packHalf2x16
+#define unpackHalf2x16 godot_unpackHalf2x16
+#define packUnorm2x16 godot_packUnorm2x16
+#define unpackUnorm2x16 godot_unpackUnorm2x16
+#define packSnorm2x16 godot_packSnorm2x16
+#define unpackSnorm2x16 godot_unpackSnorm2x16
+
+#endif // USE_HALF2FLOAT
+
+// Always expose these as they are ES310 functions and not available in ES300 or GLSL 330.
+
uint godot_packUnorm4x8(vec4 v) {
uvec4 uv = uvec4(round(clamp(v, vec4(0.0), vec4(1.0)) * 255.0));
return uv.x | (uv.y << uint(8)) | (uv.z << uint(16)) | (uv.w << uint(24));
@@ -73,9 +87,3 @@ vec4 godot_unpackSnorm4x8(uint p) {
#define unpackUnorm4x8 godot_unpackUnorm4x8
#define packSnorm4x8 godot_packSnorm4x8
#define unpackSnorm4x8 godot_unpackSnorm4x8
-#define packHalf2x16 godot_packHalf2x16
-#define unpackHalf2x16 godot_unpackHalf2x16
-#define packUnorm2x16 godot_packUnorm2x16
-#define unpackUnorm2x16 godot_unpackUnorm2x16
-#define packSnorm2x16 godot_packSnorm2x16
-#define unpackSnorm2x16 godot_unpackSnorm2x16
diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp
index 722ebbe256..34990632a4 100644
--- a/drivers/gles3/storage/config.cpp
+++ b/drivers/gles3/storage/config.cpp
@@ -233,6 +233,13 @@ Config::Config() {
} else if (rendering_device_name == "PowerVR Rogue GE8320") {
disable_transform_feedback_shader_cache = true;
}
+
+ if (OS::get_singleton()->get_current_rendering_driver_name() == "opengl3_angle") {
+ polyfill_half2float = false;
+ }
+#ifdef WEB_ENABLED
+ polyfill_half2float = false;
+#endif
}
Config::~Config() {
diff --git a/drivers/gles3/storage/config.h b/drivers/gles3/storage/config.h
index 25f5ff3370..7e7b8f1e43 100644
--- a/drivers/gles3/storage/config.h
+++ b/drivers/gles3/storage/config.h
@@ -97,13 +97,16 @@ public:
bool multiview_supported = false;
bool external_texture_supported = false;
- // Adreno 3XX compatibility
- bool disable_particles_workaround = false; // set to 'true' to disable 'GPUParticles'
+ // Adreno 3XX compatibility.
+ bool disable_particles_workaround = false; // Set to 'true' to disable 'GPUParticles'.
bool flip_xy_workaround = false;
- // PowerVR GE 8320 workaround
+ // PowerVR GE 8320 workaround.
bool disable_transform_feedback_shader_cache = false;
+ // ANGLE shader workaround.
+ bool polyfill_half2float = true;
+
#ifdef ANDROID_ENABLED
PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC eglFramebufferTextureMultiviewOVR = nullptr;
PFNGLTEXSTORAGE3DMULTISAMPLEPROC eglTexStorage3DMultisample = nullptr;
diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp
index e6c199caaa..f685295481 100644
--- a/editor/debugger/editor_debugger_node.cpp
+++ b/editor/debugger/editor_debugger_node.cpp
@@ -107,6 +107,7 @@ ScriptEditorDebugger *EditorDebuggerNode::_add_debugger() {
node->connect("breakpoint_selected", callable_mp(this, &EditorDebuggerNode::_error_selected).bind(id));
node->connect("clear_execution", callable_mp(this, &EditorDebuggerNode::_clear_execution));
node->connect("breaked", callable_mp(this, &EditorDebuggerNode::_breaked).bind(id));
+ node->connect("remote_tree_select_requested", callable_mp(this, &EditorDebuggerNode::_remote_tree_select_requested).bind(id));
node->connect("remote_tree_updated", callable_mp(this, &EditorDebuggerNode::_remote_tree_updated).bind(id));
node->connect("remote_object_updated", callable_mp(this, &EditorDebuggerNode::_remote_object_updated).bind(id));
node->connect("remote_object_property_updated", callable_mp(this, &EditorDebuggerNode::_remote_object_property_updated).bind(id));
@@ -639,6 +640,13 @@ void EditorDebuggerNode::request_remote_tree() {
get_current_debugger()->request_remote_tree();
}
+void EditorDebuggerNode::_remote_tree_select_requested(ObjectID p_id, int p_debugger) {
+ if (p_debugger != tabs->get_current_tab()) {
+ return;
+ }
+ remote_scene_tree->select_node(p_id);
+}
+
void EditorDebuggerNode::_remote_tree_updated(int p_debugger) {
if (p_debugger != tabs->get_current_tab()) {
return;
diff --git a/editor/debugger/editor_debugger_node.h b/editor/debugger/editor_debugger_node.h
index ea31af3710..0cbd2e4293 100644
--- a/editor/debugger/editor_debugger_node.h
+++ b/editor/debugger/editor_debugger_node.h
@@ -53,11 +53,8 @@ class EditorDebuggerNode : public MarginContainer {
public:
enum CameraOverride {
OVERRIDE_NONE,
- OVERRIDE_2D,
- OVERRIDE_3D_1, // 3D Viewport 1
- OVERRIDE_3D_2, // 3D Viewport 2
- OVERRIDE_3D_3, // 3D Viewport 3
- OVERRIDE_3D_4 // 3D Viewport 4
+ OVERRIDE_INGAME,
+ OVERRIDE_EDITORS,
};
private:
@@ -134,6 +131,7 @@ protected:
void _debugger_stopped(int p_id);
void _debugger_wants_stop(int p_id);
void _debugger_changed(int p_tab);
+ void _remote_tree_select_requested(ObjectID p_id, int p_debugger);
void _remote_tree_updated(int p_debugger);
void _remote_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
void _remote_object_updated(ObjectID p_id, int p_debugger);
diff --git a/editor/debugger/editor_debugger_tree.cpp b/editor/debugger/editor_debugger_tree.cpp
index 4a63025122..85727c7895 100644
--- a/editor/debugger/editor_debugger_tree.cpp
+++ b/editor/debugger/editor_debugger_tree.cpp
@@ -32,6 +32,7 @@
#include "editor_debugger_tree.h"
+#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/gui/editor_file_dialog.h"
@@ -150,7 +151,8 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
updating_scene_tree = true;
const String last_path = get_selected_path();
const String filter = SceneTreeDock::get_singleton()->get_filter();
- bool filter_changed = filter != last_filter;
+ bool should_scroll = scrolling_to_item || filter != last_filter;
+ scrolling_to_item = false;
TreeItem *scroll_item = nullptr;
// Nodes are in a flatten list, depth first. Use a stack of parents, avoid recursion.
@@ -187,8 +189,18 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
// Select previously selected node.
if (debugger_id == p_debugger) { // Can use remote id.
if (node.id == inspected_object_id) {
+ if (selection_uncollapse_all) {
+ selection_uncollapse_all = false;
+
+ // Temporarily set to `false`, to allow caching the unfolds.
+ updating_scene_tree = false;
+ item->uncollapse_tree();
+ updating_scene_tree = true;
+ }
+
item->select(0);
- if (filter_changed) {
+
+ if (should_scroll) {
scroll_item = item;
}
}
@@ -196,7 +208,7 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
if (last_path == _get_path(item)) {
updating_scene_tree = false; // Force emission of new selection.
item->select(0);
- if (filter_changed) {
+ if (should_scroll) {
scroll_item = item;
}
updating_scene_tree = true;
@@ -260,14 +272,30 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
}
}
}
- debugger_id = p_debugger; // Needed by hook, could be avoided if every debugger had its own tree
+
+ debugger_id = p_debugger; // Needed by hook, could be avoided if every debugger had its own tree.
if (scroll_item) {
- callable_mp((Tree *)this, &Tree::scroll_to_item).call_deferred(scroll_item, false);
+ scroll_to_item(scroll_item, false);
}
last_filter = filter;
updating_scene_tree = false;
}
+void EditorDebuggerTree::select_node(ObjectID p_id) {
+ // Manually select, as the tree control may be out-of-date for some reason (e.g. not shown yet).
+ selection_uncollapse_all = true;
+ inspected_object_id = uint64_t(p_id);
+ scrolling_to_item = true;
+ emit_signal(SNAME("object_selected"), inspected_object_id, debugger_id);
+
+ if (!updating_scene_tree) {
+ // Request a tree refresh.
+ EditorDebuggerNode::get_singleton()->request_remote_tree();
+ }
+ // Set the value immediately, so no update flooding happens and causes a crash.
+ updating_scene_tree = true;
+}
+
Variant EditorDebuggerTree::get_drag_data(const Point2 &p_point) {
if (get_button_id_at_position(p_point) != -1) {
return Variant();
diff --git a/editor/debugger/editor_debugger_tree.h b/editor/debugger/editor_debugger_tree.h
index ba876d95f4..1cce459972 100644
--- a/editor/debugger/editor_debugger_tree.h
+++ b/editor/debugger/editor_debugger_tree.h
@@ -51,6 +51,8 @@ private:
ObjectID inspected_object_id;
int debugger_id = 0;
bool updating_scene_tree = false;
+ bool scrolling_to_item = false;
+ bool selection_uncollapse_all = false;
HashSet<ObjectID> unfold_cache;
PopupMenu *item_menu = nullptr;
EditorFileDialog *file_dialog = nullptr;
@@ -80,6 +82,7 @@ public:
ObjectID get_selected_object();
int get_current_debugger(); // Would love to have one tree for every debugger.
void update_scene_tree(const SceneDebuggerTree *p_tree, int p_debugger);
+ void select_node(ObjectID p_id);
EditorDebuggerTree();
};
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index 3b9e6589b2..d254c582b0 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -808,6 +808,10 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, uint64_t p_thread
} else if (p_msg == "request_quit") {
emit_signal(SNAME("stop_requested"));
_stop_and_notify();
+ } else if (p_msg == "remote_node_clicked") {
+ if (!p_data.is_empty()) {
+ emit_signal(SNAME("remote_tree_select_requested"), p_data[0]);
+ }
} else if (p_msg == "performance:profile_names") {
Vector<StringName> monitors;
monitors.resize(p_data.size());
@@ -907,37 +911,42 @@ void ScriptEditorDebugger::_notification(int p_what) {
if (is_session_active()) {
peer->poll();
- if (camera_override == CameraOverride::OVERRIDE_2D) {
- Dictionary state = CanvasItemEditor::get_singleton()->get_state();
- float zoom = state["zoom"];
- Point2 offset = state["ofs"];
- Transform2D transform;
-
- transform.scale_basis(Size2(zoom, zoom));
- transform.columns[2] = -offset * zoom;
-
- Array msg;
- msg.push_back(transform);
- _put_msg("scene:override_camera_2D:transform", msg);
-
- } else if (camera_override >= CameraOverride::OVERRIDE_3D_1) {
- int viewport_idx = camera_override - CameraOverride::OVERRIDE_3D_1;
- Node3DEditorViewport *viewport = Node3DEditor::get_singleton()->get_editor_viewport(viewport_idx);
- Camera3D *const cam = viewport->get_camera_3d();
-
- Array msg;
- msg.push_back(cam->get_camera_transform());
- if (cam->get_projection() == Camera3D::PROJECTION_ORTHOGONAL) {
- msg.push_back(false);
- msg.push_back(cam->get_size());
- } else {
- msg.push_back(true);
- msg.push_back(cam->get_fov());
+ if (camera_override == CameraOverride::OVERRIDE_EDITORS) {
+ // CanvasItem Editor
+ {
+ Dictionary state = CanvasItemEditor::get_singleton()->get_state();
+ float zoom = state["zoom"];
+ Point2 offset = state["ofs"];
+ Transform2D transform;
+
+ transform.scale_basis(Size2(zoom, zoom));
+ transform.columns[2] = -offset * zoom;
+
+ Array msg;
+ msg.push_back(transform);
+ _put_msg("scene:transform_camera_2d", msg);
+ }
+
+ // Node3D Editor
+ {
+ Node3DEditorViewport *viewport = Node3DEditor::get_singleton()->get_last_used_viewport();
+ const Camera3D *cam = viewport->get_camera_3d();
+
+ Array msg;
+ msg.push_back(cam->get_camera_transform());
+ if (cam->get_projection() == Camera3D::PROJECTION_ORTHOGONAL) {
+ msg.push_back(false);
+ msg.push_back(cam->get_size());
+ } else {
+ msg.push_back(true);
+ msg.push_back(cam->get_fov());
+ }
+ msg.push_back(cam->get_near());
+ msg.push_back(cam->get_far());
+ _put_msg("scene:transform_camera_3d", msg);
}
- msg.push_back(cam->get_near());
- msg.push_back(cam->get_far());
- _put_msg("scene:override_camera_3D:transform", msg);
}
+
if (is_breaked() && can_request_idle_draw) {
_put_msg("servers:draw", Array());
can_request_idle_draw = false;
@@ -1471,23 +1480,10 @@ CameraOverride ScriptEditorDebugger::get_camera_override() const {
}
void ScriptEditorDebugger::set_camera_override(CameraOverride p_override) {
- if (p_override == CameraOverride::OVERRIDE_2D && camera_override != CameraOverride::OVERRIDE_2D) {
- Array msg;
- msg.push_back(true);
- _put_msg("scene:override_camera_2D:set", msg);
- } else if (p_override != CameraOverride::OVERRIDE_2D && camera_override == CameraOverride::OVERRIDE_2D) {
- Array msg;
- msg.push_back(false);
- _put_msg("scene:override_camera_2D:set", msg);
- } else if (p_override >= CameraOverride::OVERRIDE_3D_1 && camera_override < CameraOverride::OVERRIDE_3D_1) {
- Array msg;
- msg.push_back(true);
- _put_msg("scene:override_camera_3D:set", msg);
- } else if (p_override < CameraOverride::OVERRIDE_3D_1 && camera_override >= CameraOverride::OVERRIDE_3D_1) {
- Array msg;
- msg.push_back(false);
- _put_msg("scene:override_camera_3D:set", msg);
- }
+ Array msg;
+ msg.push_back(p_override != CameraOverride::OVERRIDE_NONE);
+ msg.push_back(p_override == CameraOverride::OVERRIDE_EDITORS);
+ _put_msg("scene:override_cameras", msg);
camera_override = p_override;
}
@@ -1778,6 +1774,7 @@ void ScriptEditorDebugger::_bind_methods() {
ADD_SIGNAL(MethodInfo("remote_object_updated", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("remote_object_property_updated", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property")));
ADD_SIGNAL(MethodInfo("remote_tree_updated"));
+ ADD_SIGNAL(MethodInfo("remote_tree_select_requested", PropertyInfo(Variant::NODE_PATH, "path")));
ADD_SIGNAL(MethodInfo("output", PropertyInfo(Variant::STRING, "msg"), PropertyInfo(Variant::INT, "level")));
ADD_SIGNAL(MethodInfo("stack_dump", PropertyInfo(Variant::ARRAY, "stack_dump")));
ADD_SIGNAL(MethodInfo("stack_frame_vars", PropertyInfo(Variant::INT, "num_vars")));
diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp
index 2913d51794..8ceaa8e85c 100644
--- a/editor/editor_feature_profile.cpp
+++ b/editor/editor_feature_profile.cpp
@@ -45,6 +45,7 @@
const char *EditorFeatureProfile::feature_names[FEATURE_MAX] = {
TTRC("3D Editor"),
TTRC("Script Editor"),
+ TTRC("Game View"),
TTRC("Asset Library"),
TTRC("Scene Tree Editing"),
TTRC("Node Dock"),
@@ -56,6 +57,7 @@ const char *EditorFeatureProfile::feature_names[FEATURE_MAX] = {
const char *EditorFeatureProfile::feature_descriptions[FEATURE_MAX] = {
TTRC("Allows to view and edit 3D scenes."),
TTRC("Allows to edit scripts using the integrated script editor."),
+ TTRC("Provides tools for selecting and debugging nodes at runtime."),
TTRC("Provides built-in access to the Asset Library."),
TTRC("Allows editing the node hierarchy in the Scene dock."),
TTRC("Allows to work with signals and groups of the node selected in the Scene dock."),
@@ -67,6 +69,7 @@ const char *EditorFeatureProfile::feature_descriptions[FEATURE_MAX] = {
const char *EditorFeatureProfile::feature_identifiers[FEATURE_MAX] = {
"3d",
"script",
+ "game",
"asset_lib",
"scene_tree",
"node_dock",
@@ -309,6 +312,7 @@ void EditorFeatureProfile::_bind_methods() {
BIND_ENUM_CONSTANT(FEATURE_FILESYSTEM_DOCK);
BIND_ENUM_CONSTANT(FEATURE_IMPORT_DOCK);
BIND_ENUM_CONSTANT(FEATURE_HISTORY_DOCK);
+ BIND_ENUM_CONSTANT(FEATURE_GAME);
BIND_ENUM_CONSTANT(FEATURE_MAX);
}
diff --git a/editor/editor_feature_profile.h b/editor/editor_feature_profile.h
index 58ec41ba8d..859be5fe4e 100644
--- a/editor/editor_feature_profile.h
+++ b/editor/editor_feature_profile.h
@@ -57,6 +57,7 @@ public:
FEATURE_FILESYSTEM_DOCK,
FEATURE_IMPORT_DOCK,
FEATURE_HISTORY_DOCK,
+ FEATURE_GAME,
FEATURE_MAX
};
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index 366fc48c75..0df709f9af 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -2378,10 +2378,18 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) {
if (!is_scanning()) {
_process_update_pending();
}
- call_deferred(SNAME("emit_signal"), "filesystem_changed"); // Update later
+ if (!filesystem_changed_queued) {
+ filesystem_changed_queued = true;
+ callable_mp(this, &EditorFileSystem::_notify_filesystem_changed).call_deferred();
+ }
}
}
+void EditorFileSystem::_notify_filesystem_changed() {
+ emit_signal("filesystem_changed");
+ filesystem_changed_queued = false;
+}
+
HashSet<String> EditorFileSystem::get_valid_extensions() const {
return valid_extensions;
}
diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h
index 0f0f1ec708..68e3f918fe 100644
--- a/editor/editor_file_system.h
+++ b/editor/editor_file_system.h
@@ -182,6 +182,7 @@ class EditorFileSystem : public Node {
EditorFileSystemDirectory *new_filesystem = nullptr;
ScannedDirectory *first_scan_root_dir = nullptr;
+ bool filesystem_changed_queued = false;
bool scanning = false;
bool importing = false;
bool first_scan = true;
@@ -191,6 +192,7 @@ class EditorFileSystem : public Node {
bool revalidate_import_files = false;
int nb_files_total = 0;
+ void _notify_filesystem_changed();
void _scan_filesystem();
void _first_scan_filesystem();
void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, List<String> &p_gdextension_extensions, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions);
diff --git a/editor/editor_main_screen.h b/editor/editor_main_screen.h
index 23a587179a..5a84ab75c8 100644
--- a/editor/editor_main_screen.h
+++ b/editor/editor_main_screen.h
@@ -49,6 +49,7 @@ public:
EDITOR_2D = 0,
EDITOR_3D,
EDITOR_SCRIPT,
+ EDITOR_GAME,
EDITOR_ASSETLIB,
};
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index c3c925936d..e2351916b9 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -147,6 +147,7 @@
#include "editor/plugins/editor_plugin.h"
#include "editor/plugins/editor_preview_plugins.h"
#include "editor/plugins/editor_resource_conversion_plugin.h"
+#include "editor/plugins/game_view_plugin.h"
#include "editor/plugins/gdextension_export_plugin.h"
#include "editor/plugins/material_editor_plugin.h"
#include "editor/plugins/mesh_library_editor_plugin.h"
@@ -359,6 +360,8 @@ void EditorNode::shortcut_input(const Ref<InputEvent> &p_event) {
editor_main_screen->select(EditorMainScreen::EDITOR_3D);
} else if (ED_IS_SHORTCUT("editor/editor_script", p_event)) {
editor_main_screen->select(EditorMainScreen::EDITOR_SCRIPT);
+ } else if (ED_IS_SHORTCUT("editor/editor_game", p_event)) {
+ editor_main_screen->select(EditorMainScreen::EDITOR_GAME);
} else if (ED_IS_SHORTCUT("editor/editor_help", p_event)) {
emit_signal(SNAME("request_help_search"), "");
} else if (ED_IS_SHORTCUT("editor/editor_assetlib", p_event) && AssetLibraryEditorPlugin::is_available()) {
@@ -6579,6 +6582,7 @@ void EditorNode::_feature_profile_changed() {
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_3D, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D));
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_SCRIPT, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT));
+ editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_GAME, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_GAME));
if (AssetLibraryEditorPlugin::is_available()) {
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_ASSETLIB, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB));
}
@@ -6589,6 +6593,7 @@ void EditorNode::_feature_profile_changed() {
editor_dock_manager->set_dock_enabled(history_dock, true);
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_3D, true);
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_SCRIPT, true);
+ editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_GAME, true);
if (AssetLibraryEditorPlugin::is_available()) {
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_ASSETLIB, true);
}
@@ -7720,6 +7725,7 @@ EditorNode::EditorNode() {
add_editor_plugin(memnew(CanvasItemEditorPlugin));
add_editor_plugin(memnew(Node3DEditorPlugin));
add_editor_plugin(memnew(ScriptEditorPlugin));
+ add_editor_plugin(memnew(GameViewPlugin));
EditorAudioBuses *audio_bus_editor = EditorAudioBuses::register_editor();
@@ -7902,12 +7908,14 @@ EditorNode::EditorNode() {
ED_SHORTCUT_AND_COMMAND("editor/editor_2d", TTR("Open 2D Editor"), KeyModifierMask::CTRL | Key::F1);
ED_SHORTCUT_AND_COMMAND("editor/editor_3d", TTR("Open 3D Editor"), KeyModifierMask::CTRL | Key::F2);
ED_SHORTCUT_AND_COMMAND("editor/editor_script", TTR("Open Script Editor"), KeyModifierMask::CTRL | Key::F3);
- ED_SHORTCUT_AND_COMMAND("editor/editor_assetlib", TTR("Open Asset Library"), KeyModifierMask::CTRL | Key::F4);
+ ED_SHORTCUT_AND_COMMAND("editor/editor_game", TTR("Open Game View"), KeyModifierMask::CTRL | Key::F4);
+ ED_SHORTCUT_AND_COMMAND("editor/editor_assetlib", TTR("Open Asset Library"), KeyModifierMask::CTRL | Key::F5);
ED_SHORTCUT_OVERRIDE("editor/editor_2d", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_1);
ED_SHORTCUT_OVERRIDE("editor/editor_3d", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_2);
ED_SHORTCUT_OVERRIDE("editor/editor_script", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_3);
- ED_SHORTCUT_OVERRIDE("editor/editor_assetlib", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_4);
+ ED_SHORTCUT_OVERRIDE("editor/editor_game", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_4);
+ ED_SHORTCUT_OVERRIDE("editor/editor_assetlib", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_5);
ED_SHORTCUT_AND_COMMAND("editor/editor_next", TTR("Open the next Editor"));
ED_SHORTCUT_AND_COMMAND("editor/editor_prev", TTR("Open the previous Editor"));
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 6f2dbbe1fd..533e808c6d 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -2474,9 +2474,6 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected
make_dir_dialog->popup_centered();
} break;
- case FILE_INFO: {
- } break;
-
case FILE_REIMPORT: {
ImportDock::get_singleton()->reimport_resources(p_selected);
} break;
diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h
index 0a61c835bc..a3cbc0faac 100644
--- a/editor/filesystem_dock.h
+++ b/editor/filesystem_dock.h
@@ -118,7 +118,6 @@ private:
FILE_REMOVE,
FILE_DUPLICATE,
FILE_REIMPORT,
- FILE_INFO,
FILE_NEW,
FILE_SHOW_IN_EXPLORER,
FILE_OPEN_EXTERNAL,
diff --git a/editor/icons/2DNodes.svg b/editor/icons/2DNodes.svg
new file mode 100644
index 0000000000..90b92a4bc7
--- /dev/null
+++ b/editor/icons/2DNodes.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16"><path fill="none" stroke="#8da5f3" stroke-width="2" d="M 8,13 C 5.2385763,13 3,10.761424 3,8 3,5.2385763 5.2385763,3 8,3"/><path fill="none" stroke="#8eef97" stroke-width="2" d="m 8,13 c 2.761424,0 5,-2.238576 5,-5 C 13,5.2385763 10.761424,3 8,3"/></svg> \ No newline at end of file
diff --git a/editor/icons/Camera.svg b/editor/icons/Camera.svg
new file mode 100644
index 0000000000..8612d458a7
--- /dev/null
+++ b/editor/icons/Camera.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16"><path fill="#e0e0e0" d="M9 2a3 3 0 0 0-3 2.777 3 3 0 1 0-3 5.047V12a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-1l3 2V7l-3 2V7.23A3 3 0 0 0 9 2z"/></svg> \ No newline at end of file
diff --git a/editor/icons/Game.svg b/editor/icons/Game.svg
new file mode 100644
index 0000000000..e75e5c5312
--- /dev/null
+++ b/editor/icons/Game.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M 1,15 V 12 C 1,11.5 1.5,11 2,11 H 3 V 10 C 3,9.5 3.5,9 4,9 h 1 c 0.5,0 1,0.5 1,1 v 1 H 8 V 5 h 2 v 6 h 4 c 0.5,0 1,0.5 1,1 v 3 z"/><circle cx="9" cy="4" r="3" fill="#e0e0e0"/></svg> \ No newline at end of file
diff --git a/editor/icons/NextFrame.svg b/editor/icons/NextFrame.svg
new file mode 100644
index 0000000000..9609b2538b
--- /dev/null
+++ b/editor/icons/NextFrame.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m 12,3 c -0.552285,0 -1,0.4477153 -1,1 v 8 c 0,0.552285 0.447715,1 1,1 h 1 c 0.552285,0 1,-0.447715 1,-1 V 4 C 14,3.4477153 13.552285,3 13,3 Z M 2.975,3.002 C 2.4332786,3.0155465 2.0009144,3.45811 2,4 v 8 c -3.148e-4,0.838862 0.9701632,1.305289 1.625,0.781 l 5,-4 c 0.4989606,-0.4003069 0.4989606,-1.1596931 0,-1.56 l -5,-4 C 3.4409271,3.0736532 3.2107095,2.9960875 2.975,3.002 Z"/></svg> \ No newline at end of file
diff --git a/editor/import/3d/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp
index a2cf2215bb..c1969faf42 100644
--- a/editor/import/3d/resource_importer_obj.cpp
+++ b/editor/import/3d/resource_importer_obj.cpp
@@ -541,7 +541,7 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes,
if (p_generate_lods) {
// Use normal merge/split angles that match the defaults used for 3D scene importing.
- mesh->generate_lods(60.0f, 25.0f, {});
+ mesh->generate_lods(60.0f, {});
}
if (p_generate_shadow_mesh) {
diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp
index 50c5972028..1e5a7dc72b 100644
--- a/editor/import/3d/resource_importer_scene.cpp
+++ b/editor/import/3d/resource_importer_scene.cpp
@@ -2045,9 +2045,7 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/shadow_meshes", PROPERTY_HINT_ENUM, "Default,Enable,Disable"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/lightmap_uv", PROPERTY_HINT_ENUM, "Default,Enable,Disable"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/lods", PROPERTY_HINT_ENUM, "Default,Enable,Disable"), 0));
- r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "lods/normal_split_angle", PROPERTY_HINT_RANGE, "0,180,0.1,degrees"), 25.0f));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "lods/normal_merge_angle", PROPERTY_HINT_RANGE, "0,180,0.1,degrees"), 60.0f));
- r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "lods/raycast_normals", PROPERTY_HINT_NONE, ""), false));
} break;
case INTERNAL_IMPORT_CATEGORY_MATERIAL: {
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "use_external/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
@@ -2476,9 +2474,7 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
//do mesh processing
bool generate_lods = p_generate_lods;
- float split_angle = 25.0f;
float merge_angle = 60.0f;
- bool raycast_normals = false;
bool create_shadow_meshes = p_create_shadow_meshes;
bool bake_lightmaps = p_light_bake_mode == LIGHT_BAKE_STATIC_LIGHTMAPS;
String save_to_file;
@@ -2525,18 +2521,10 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
}
}
- if (mesh_settings.has("lods/normal_split_angle")) {
- split_angle = mesh_settings["lods/normal_split_angle"];
- }
-
if (mesh_settings.has("lods/normal_merge_angle")) {
merge_angle = mesh_settings["lods/normal_merge_angle"];
}
- if (mesh_settings.has("lods/raycast_normals")) {
- raycast_normals = mesh_settings["lods/raycast_normals"];
- }
-
if (bool(mesh_settings.get("save_to_file/enabled", false))) {
save_to_file = mesh_settings.get("save_to_file/path", String());
if (!save_to_file.is_resource_file()) {
@@ -2585,7 +2573,7 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
if (generate_lods) {
Array skin_pose_transform_array = _get_skinned_pose_transforms(src_mesh_node);
- src_mesh_node->get_mesh()->generate_lods(merge_angle, split_angle, skin_pose_transform_array, raycast_normals);
+ src_mesh_node->get_mesh()->generate_lods(merge_angle, skin_pose_transform_array);
}
if (create_shadow_meshes) {
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index c897b4ae70..f7276cb416 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -3979,7 +3979,6 @@ void CanvasItemEditor::_update_editor_settings() {
grid_snap_button->set_button_icon(get_editor_theme_icon(SNAME("SnapGrid")));
snap_config_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
skeleton_menu->set_button_icon(get_editor_theme_icon(SNAME("Bone")));
- override_camera_button->set_button_icon(get_editor_theme_icon(SNAME("Camera2D")));
pan_button->set_button_icon(get_editor_theme_icon(SNAME("ToolPan")));
ruler_button->set_button_icon(get_editor_theme_icon(SNAME("Ruler")));
pivot_button->set_button_icon(get_editor_theme_icon(SNAME("EditPivot")));
@@ -4018,8 +4017,6 @@ void CanvasItemEditor::_notification(int p_what) {
case NOTIFICATION_READY: {
_update_lock_and_group_button();
- EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(true));
- EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(false));
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &CanvasItemEditor::_project_settings_changed));
} break;
@@ -4118,15 +4115,6 @@ void CanvasItemEditor::_notification(int p_what) {
_update_editor_settings();
} break;
- case NOTIFICATION_VISIBILITY_CHANGED: {
- if (!is_visible() && override_camera_button->is_pressed()) {
- EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton();
-
- debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_NONE);
- override_camera_button->set_pressed(false);
- }
- } break;
-
case NOTIFICATION_APPLICATION_FOCUS_OUT:
case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {
if (drag_type != DRAG_NONE) {
@@ -4284,16 +4272,6 @@ void CanvasItemEditor::_button_toggle_grid_snap(bool p_status) {
viewport->queue_redraw();
}
-void CanvasItemEditor::_button_override_camera(bool p_pressed) {
- EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton();
-
- if (p_pressed) {
- debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_2D);
- } else {
- debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_NONE);
- }
-}
-
void CanvasItemEditor::_button_tool_select(int p_index) {
Button *tb[TOOL_MAX] = { select_button, list_select_button, move_button, scale_button, rotate_button, pivot_button, pan_button, ruler_button };
for (int i = 0; i < TOOL_MAX; i++) {
@@ -4400,17 +4378,6 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
te->commit_insert_queue();
}
-void CanvasItemEditor::_update_override_camera_button(bool p_game_running) {
- if (p_game_running) {
- override_camera_button->set_disabled(false);
- override_camera_button->set_tooltip_text(TTR("Project Camera Override\nOverrides the running project's camera with the editor viewport camera."));
- } else {
- override_camera_button->set_disabled(true);
- override_camera_button->set_pressed(false);
- override_camera_button->set_tooltip_text(TTR("Project Camera Override\nNo project instance running. Run the project from the editor to use this feature."));
- }
-}
-
void CanvasItemEditor::_popup_callback(int p_op) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
last_option = MenuOption(p_op);
@@ -5516,16 +5483,6 @@ CanvasItemEditor::CanvasItemEditor() {
main_menu_hbox->add_child(memnew(VSeparator));
- override_camera_button = memnew(Button);
- override_camera_button->set_theme_type_variation("FlatButton");
- main_menu_hbox->add_child(override_camera_button);
- override_camera_button->connect(SceneStringName(toggled), callable_mp(this, &CanvasItemEditor::_button_override_camera));
- override_camera_button->set_toggle_mode(true);
- override_camera_button->set_disabled(true);
- _update_override_camera_button(false);
-
- main_menu_hbox->add_child(memnew(VSeparator));
-
view_menu = memnew(MenuButton);
view_menu->set_flat(false);
view_menu->set_theme_type_variation("FlatMenuButton");
diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h
index c648655036..487c24baf4 100644
--- a/editor/plugins/canvas_item_editor_plugin.h
+++ b/editor/plugins/canvas_item_editor_plugin.h
@@ -337,7 +337,6 @@ private:
Button *group_button = nullptr;
Button *ungroup_button = nullptr;
- Button *override_camera_button = nullptr;
MenuButton *view_menu = nullptr;
PopupMenu *grid_menu = nullptr;
PopupMenu *theme_menu = nullptr;
@@ -520,11 +519,8 @@ private:
void _zoom_on_position(real_t p_zoom, Point2 p_position = Point2());
void _button_toggle_smart_snap(bool p_status);
void _button_toggle_grid_snap(bool p_status);
- void _button_override_camera(bool p_pressed);
void _button_tool_select(int p_index);
- void _update_override_camera_button(bool p_game_running);
-
HSplitContainer *left_panel_split = nullptr;
HSplitContainer *right_panel_split = nullptr;
VSplitContainer *bottom_split = nullptr;
diff --git a/editor/plugins/game_view_plugin.cpp b/editor/plugins/game_view_plugin.cpp
new file mode 100644
index 0000000000..358336a806
--- /dev/null
+++ b/editor/plugins/game_view_plugin.cpp
@@ -0,0 +1,480 @@
+/**************************************************************************/
+/* game_view_plugin.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* REDOT ENGINE */
+/* https://redotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2024-present Redot Engine contributors */
+/* (see REDOT_AUTHORS.md) */
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "game_view_plugin.h"
+
+#include "editor/editor_main_screen.h"
+#include "editor/editor_node.h"
+#include "editor/editor_settings.h"
+#include "editor/themes/editor_scale.h"
+#include "scene/gui/button.h"
+#include "scene/gui/menu_button.h"
+#include "scene/gui/panel.h"
+#include "scene/gui/separator.h"
+
+void GameViewDebugger::_session_started(Ref<EditorDebuggerSession> p_session) {
+ p_session->send_message("scene:runtime_node_select_setup", Array());
+
+ Array type;
+ type.append(node_type);
+ p_session->send_message("scene:runtime_node_select_set_type", type);
+ Array visible;
+ visible.append(selection_visible);
+ p_session->send_message("scene:runtime_node_select_set_visible", visible);
+ Array mode;
+ mode.append(select_mode);
+ p_session->send_message("scene:runtime_node_select_set_mode", mode);
+
+ emit_signal(SNAME("session_started"));
+}
+
+void GameViewDebugger::_session_stopped() {
+ emit_signal(SNAME("session_stopped"));
+}
+
+void GameViewDebugger::set_suspend(bool p_enabled) {
+ Array message;
+ message.append(p_enabled);
+
+ for (Ref<EditorDebuggerSession> &I : sessions) {
+ if (I->is_active()) {
+ I->send_message("scene:suspend_changed", message);
+ }
+ }
+}
+
+void GameViewDebugger::next_frame() {
+ for (Ref<EditorDebuggerSession> &I : sessions) {
+ if (I->is_active()) {
+ I->send_message("scene:next_frame", Array());
+ }
+ }
+}
+
+void GameViewDebugger::set_node_type(int p_type) {
+ node_type = p_type;
+
+ Array message;
+ message.append(p_type);
+
+ for (Ref<EditorDebuggerSession> &I : sessions) {
+ if (I->is_active()) {
+ I->send_message("scene:runtime_node_select_set_type", message);
+ }
+ }
+}
+
+void GameViewDebugger::set_selection_visible(bool p_visible) {
+ selection_visible = p_visible;
+
+ Array message;
+ message.append(p_visible);
+
+ for (Ref<EditorDebuggerSession> &I : sessions) {
+ if (I->is_active()) {
+ I->send_message("scene:runtime_node_select_set_visible", message);
+ }
+ }
+}
+
+void GameViewDebugger::set_select_mode(int p_mode) {
+ select_mode = p_mode;
+
+ Array message;
+ message.append(p_mode);
+
+ for (Ref<EditorDebuggerSession> &I : sessions) {
+ if (I->is_active()) {
+ I->send_message("scene:runtime_node_select_set_mode", message);
+ }
+ }
+}
+
+void GameViewDebugger::set_camera_override(bool p_enabled) {
+ EditorDebuggerNode::get_singleton()->set_camera_override(p_enabled ? camera_override_mode : EditorDebuggerNode::OVERRIDE_NONE);
+}
+
+void GameViewDebugger::set_camera_manipulate_mode(EditorDebuggerNode::CameraOverride p_mode) {
+ camera_override_mode = p_mode;
+
+ if (EditorDebuggerNode::get_singleton()->get_camera_override() != EditorDebuggerNode::OVERRIDE_NONE) {
+ set_camera_override(true);
+ }
+}
+
+void GameViewDebugger::reset_camera_2d_position() {
+ for (Ref<EditorDebuggerSession> &I : sessions) {
+ if (I->is_active()) {
+ I->send_message("scene:runtime_node_select_reset_camera_2d", Array());
+ }
+ }
+}
+
+void GameViewDebugger::reset_camera_3d_position() {
+ for (Ref<EditorDebuggerSession> &I : sessions) {
+ if (I->is_active()) {
+ I->send_message("scene:runtime_node_select_reset_camera_3d", Array());
+ }
+ }
+}
+
+void GameViewDebugger::setup_session(int p_session_id) {
+ Ref<EditorDebuggerSession> session = get_session(p_session_id);
+ ERR_FAIL_COND(session.is_null());
+
+ sessions.append(session);
+
+ session->connect("started", callable_mp(this, &GameViewDebugger::_session_started).bind(session));
+ session->connect("stopped", callable_mp(this, &GameViewDebugger::_session_stopped));
+}
+
+void GameViewDebugger::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("session_started"));
+ ADD_SIGNAL(MethodInfo("session_stopped"));
+}
+
+///////
+
+void GameView::_sessions_changed() {
+ // The debugger session's `session_started/stopped` signal can be unreliable, so count it manually.
+ active_sessions = 0;
+ Array sessions = debugger->get_sessions();
+ for (int i = 0; i < sessions.size(); i++) {
+ if (Object::cast_to<EditorDebuggerSession>(sessions[i])->is_active()) {
+ active_sessions++;
+ }
+ }
+
+ _update_debugger_buttons();
+}
+
+void GameView::_update_debugger_buttons() {
+ bool empty = active_sessions == 0;
+
+ suspend_button->set_disabled(empty);
+ camera_override_button->set_disabled(empty);
+
+ PopupMenu *menu = camera_override_menu->get_popup();
+
+ bool disable_camera_reset = empty || !camera_override_button->is_pressed() || !menu->is_item_checked(menu->get_item_index(CAMERA_MODE_INGAME));
+ menu->set_item_disabled(CAMERA_RESET_2D, disable_camera_reset);
+ menu->set_item_disabled(CAMERA_RESET_3D, disable_camera_reset);
+
+ if (empty) {
+ suspend_button->set_pressed(false);
+ camera_override_button->set_pressed(false);
+ }
+ next_frame_button->set_disabled(!suspend_button->is_pressed());
+}
+
+void GameView::_suspend_button_toggled(bool p_pressed) {
+ _update_debugger_buttons();
+
+ debugger->set_suspend(p_pressed);
+}
+
+void GameView::_node_type_pressed(int p_option) {
+ RuntimeNodeSelect::NodeType type = (RuntimeNodeSelect::NodeType)p_option;
+ for (int i = 0; i < RuntimeNodeSelect::NODE_TYPE_MAX; i++) {
+ node_type_button[i]->set_pressed_no_signal(i == type);
+ }
+
+ _update_debugger_buttons();
+
+ debugger->set_node_type(type);
+}
+
+void GameView::_select_mode_pressed(int p_option) {
+ RuntimeNodeSelect::SelectMode mode = (RuntimeNodeSelect::SelectMode)p_option;
+ for (int i = 0; i < RuntimeNodeSelect::SELECT_MODE_MAX; i++) {
+ select_mode_button[i]->set_pressed_no_signal(i == mode);
+ }
+
+ debugger->set_select_mode(mode);
+}
+
+void GameView::_hide_selection_toggled(bool p_pressed) {
+ hide_selection->set_button_icon(get_editor_theme_icon(p_pressed ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible")));
+
+ debugger->set_selection_visible(!p_pressed);
+}
+
+void GameView::_camera_override_button_toggled(bool p_pressed) {
+ _update_debugger_buttons();
+
+ debugger->set_camera_override(p_pressed);
+}
+
+void GameView::_camera_override_menu_id_pressed(int p_id) {
+ PopupMenu *menu = camera_override_menu->get_popup();
+ if (p_id != CAMERA_RESET_2D && p_id != CAMERA_RESET_3D) {
+ for (int i = 0; i < menu->get_item_count(); i++) {
+ menu->set_item_checked(i, false);
+ }
+ }
+
+ switch (p_id) {
+ case CAMERA_RESET_2D: {
+ debugger->reset_camera_2d_position();
+ } break;
+ case CAMERA_RESET_3D: {
+ debugger->reset_camera_3d_position();
+ } break;
+ case CAMERA_MODE_INGAME: {
+ debugger->set_camera_manipulate_mode(EditorDebuggerNode::OVERRIDE_INGAME);
+ menu->set_item_checked(menu->get_item_index(p_id), true);
+
+ _update_debugger_buttons();
+ } break;
+ case CAMERA_MODE_EDITORS: {
+ debugger->set_camera_manipulate_mode(EditorDebuggerNode::OVERRIDE_EDITORS);
+ menu->set_item_checked(menu->get_item_index(p_id), true);
+
+ _update_debugger_buttons();
+ } break;
+ }
+}
+
+void GameView::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_THEME_CHANGED: {
+ suspend_button->set_button_icon(get_editor_theme_icon(SNAME("Pause")));
+ next_frame_button->set_button_icon(get_editor_theme_icon(SNAME("NextFrame")));
+
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_button_icon(get_editor_theme_icon(SNAME("InputEventJoypadMotion")));
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->set_button_icon(get_editor_theme_icon(SNAME("2DNodes")));
+#ifndef _3D_DISABLED
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_button_icon(get_editor_theme_icon(SNAME("Node3D")));
+#endif // _3D_DISABLED
+
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_button_icon(get_editor_theme_icon(SNAME("ToolSelect")));
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_button_icon(get_editor_theme_icon(SNAME("ListSelect")));
+
+ hide_selection->set_button_icon(get_editor_theme_icon(hide_selection->is_pressed() ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible")));
+
+ camera_override_button->set_button_icon(get_editor_theme_icon(SNAME("Camera")));
+ camera_override_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
+ } break;
+ }
+}
+
+void GameView::set_state(const Dictionary &p_state) {
+ if (p_state.has("hide_selection")) {
+ hide_selection->set_pressed(p_state["hide_selection"]);
+ _hide_selection_toggled(hide_selection->is_pressed());
+ }
+ if (p_state.has("select_mode")) {
+ _select_mode_pressed(p_state["select_mode"]);
+ }
+ if (p_state.has("camera_override_mode")) {
+ _camera_override_menu_id_pressed(p_state["camera_override_mode"]);
+ }
+}
+
+Dictionary GameView::get_state() const {
+ Dictionary d;
+ d["hide_selection"] = hide_selection->is_pressed();
+
+ for (int i = 0; i < RuntimeNodeSelect::SELECT_MODE_MAX; i++) {
+ if (select_mode_button[i]->is_pressed()) {
+ d["select_mode"] = i;
+ break;
+ }
+ }
+
+ PopupMenu *menu = camera_override_menu->get_popup();
+ for (int i = CAMERA_MODE_INGAME; i < CAMERA_MODE_EDITORS + 1; i++) {
+ if (menu->is_item_checked(menu->get_item_index(i))) {
+ d["camera_override_mode"] = i;
+ break;
+ }
+ }
+
+ return d;
+}
+
+GameView::GameView(Ref<GameViewDebugger> p_debugger) {
+ debugger = p_debugger;
+
+ // 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);
+ toolbar_margin->add_theme_constant_override("margin_left", 4 * EDSCALE);
+ toolbar_margin->add_theme_constant_override("margin_right", 4 * EDSCALE);
+ add_child(toolbar_margin);
+
+ HBoxContainer *main_menu_hbox = memnew(HBoxContainer);
+ toolbar_margin->add_child(main_menu_hbox);
+
+ suspend_button = memnew(Button);
+ main_menu_hbox->add_child(suspend_button);
+ suspend_button->set_toggle_mode(true);
+ suspend_button->set_theme_type_variation("FlatButton");
+ suspend_button->connect(SceneStringName(toggled), callable_mp(this, &GameView::_suspend_button_toggled));
+ suspend_button->set_tooltip_text(TTR("Suspend"));
+
+ next_frame_button = memnew(Button);
+ main_menu_hbox->add_child(next_frame_button);
+ next_frame_button->set_theme_type_variation("FlatButton");
+ next_frame_button->connect(SceneStringName(pressed), callable_mp(*debugger, &GameViewDebugger::next_frame));
+ next_frame_button->set_tooltip_text(TTR("Next Frame"));
+
+ main_menu_hbox->add_child(memnew(VSeparator));
+
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE] = memnew(Button);
+ main_menu_hbox->add_child(node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]);
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_text(TTR("Input"));
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_toggle_mode(true);
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_pressed(true);
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_theme_type_variation("FlatButton");
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_node_type_pressed).bind(RuntimeNodeSelect::NODE_TYPE_NONE));
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_tooltip_text(TTR("Allow game input."));
+
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_2D] = memnew(Button);
+ main_menu_hbox->add_child(node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]);
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->set_text(TTR("2D"));
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->set_toggle_mode(true);
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->set_theme_type_variation("FlatButton");
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_node_type_pressed).bind(RuntimeNodeSelect::NODE_TYPE_2D));
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->set_tooltip_text(TTR("Disable game input and allow to select Node2Ds, Controls, and manipulate the 2D camera."));
+
+#ifndef _3D_DISABLED
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_3D] = memnew(Button);
+ main_menu_hbox->add_child(node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]);
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_text(TTR("3D"));
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_toggle_mode(true);
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_theme_type_variation("FlatButton");
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_node_type_pressed).bind(RuntimeNodeSelect::NODE_TYPE_3D));
+ node_type_button[RuntimeNodeSelect::NODE_TYPE_3D]->set_tooltip_text(TTR("Disable game input and allow to select Node3Ds and manipulate the 3D camera."));
+#endif // _3D_DISABLED
+
+ main_menu_hbox->add_child(memnew(VSeparator));
+
+ hide_selection = memnew(Button);
+ main_menu_hbox->add_child(hide_selection);
+ hide_selection->set_toggle_mode(true);
+ hide_selection->set_theme_type_variation("FlatButton");
+ hide_selection->connect(SceneStringName(toggled), callable_mp(this, &GameView::_hide_selection_toggled));
+ hide_selection->set_tooltip_text(TTR("Toggle Selection Visibility"));
+
+ main_menu_hbox->add_child(memnew(VSeparator));
+
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE] = memnew(Button);
+ main_menu_hbox->add_child(select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]);
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_toggle_mode(true);
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_pressed(true);
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_theme_type_variation("FlatButton");
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_select_mode_pressed).bind(RuntimeNodeSelect::SELECT_MODE_SINGLE));
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_select", TTR("Select Mode"), Key::Q));
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_shortcut_context(this);
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_tooltip_text(keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Alt+RMB: Show list of all nodes at position clicked."));
+
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST] = memnew(Button);
+ main_menu_hbox->add_child(select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]);
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_toggle_mode(true);
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_theme_type_variation("FlatButton");
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->connect(SceneStringName(pressed), callable_mp(this, &GameView::_select_mode_pressed).bind(RuntimeNodeSelect::SELECT_MODE_LIST));
+ select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_tooltip_text(TTR("Show list of selectable nodes at position clicked."));
+
+ main_menu_hbox->add_child(memnew(VSeparator));
+
+ camera_override_button = memnew(Button);
+ main_menu_hbox->add_child(camera_override_button);
+ camera_override_button->set_toggle_mode(true);
+ camera_override_button->set_theme_type_variation("FlatButton");
+ camera_override_button->connect(SceneStringName(toggled), callable_mp(this, &GameView::_camera_override_button_toggled));
+ camera_override_button->set_tooltip_text(TTR("Override the in-game camera."));
+
+ camera_override_menu = memnew(MenuButton);
+ main_menu_hbox->add_child(camera_override_menu);
+ camera_override_menu->set_flat(false);
+ camera_override_menu->set_theme_type_variation("FlatMenuButton");
+ camera_override_menu->set_h_size_flags(SIZE_SHRINK_END);
+ camera_override_menu->set_tooltip_text(TTR("Camera Override Options"));
+
+ PopupMenu *menu = camera_override_menu->get_popup();
+ menu->connect(SceneStringName(id_pressed), callable_mp(this, &GameView::_camera_override_menu_id_pressed));
+ menu->add_item(TTR("Reset 2D Camera"), CAMERA_RESET_2D);
+ menu->add_item(TTR("Reset 3D Camera"), CAMERA_RESET_3D);
+ menu->add_separator();
+ menu->add_radio_check_item(TTR("Manipulate In-Game"), CAMERA_MODE_INGAME);
+ menu->set_item_checked(menu->get_item_index(CAMERA_MODE_INGAME), true);
+ menu->add_radio_check_item(TTR("Manipulate From Editors"), CAMERA_MODE_EDITORS);
+
+ _update_debugger_buttons();
+
+ panel = memnew(Panel);
+ add_child(panel);
+ panel->set_theme_type_variation("GamePanel");
+ panel->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ p_debugger->connect("session_started", callable_mp(this, &GameView::_sessions_changed));
+ p_debugger->connect("session_stopped", callable_mp(this, &GameView::_sessions_changed));
+}
+
+///////
+
+void GameViewPlugin::make_visible(bool p_visible) {
+ game_view->set_visible(p_visible);
+}
+
+void GameViewPlugin::set_state(const Dictionary &p_state) {
+ game_view->set_state(p_state);
+}
+
+Dictionary GameViewPlugin::get_state() const {
+ return game_view->get_state();
+}
+
+void GameViewPlugin::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ add_debugger_plugin(debugger);
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+ remove_debugger_plugin(debugger);
+ } break;
+ }
+}
+
+GameViewPlugin::GameViewPlugin() {
+ debugger.instantiate();
+
+ game_view = memnew(GameView(debugger));
+ game_view->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(game_view);
+ game_view->hide();
+}
+
+GameViewPlugin::~GameViewPlugin() {
+}
diff --git a/editor/plugins/game_view_plugin.h b/editor/plugins/game_view_plugin.h
new file mode 100644
index 0000000000..c0f03ce723
--- /dev/null
+++ b/editor/plugins/game_view_plugin.h
@@ -0,0 +1,154 @@
+/**************************************************************************/
+/* game_view_plugin.h */
+/**************************************************************************/
+/* This file is part of: */
+/* REDOT ENGINE */
+/* https://redotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2024-present Redot Engine contributors */
+/* (see REDOT_AUTHORS.md) */
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef GAME_VIEW_PLUGIN_H
+#define GAME_VIEW_PLUGIN_H
+
+#include "editor/debugger/editor_debugger_node.h"
+#include "editor/plugins/editor_debugger_plugin.h"
+#include "editor/plugins/editor_plugin.h"
+#include "scene/debugger/scene_debugger.h"
+#include "scene/gui/box_container.h"
+
+class GameViewDebugger : public EditorDebuggerPlugin {
+ GDCLASS(GameViewDebugger, EditorDebuggerPlugin);
+
+private:
+ Vector<Ref<EditorDebuggerSession>> sessions;
+
+ int node_type = RuntimeNodeSelect::NODE_TYPE_NONE;
+ bool selection_visible = true;
+ int select_mode = RuntimeNodeSelect::SELECT_MODE_SINGLE;
+ EditorDebuggerNode::CameraOverride camera_override_mode = EditorDebuggerNode::OVERRIDE_INGAME;
+
+ void _session_started(Ref<EditorDebuggerSession> p_session);
+ void _session_stopped();
+
+protected:
+ static void _bind_methods();
+
+public:
+ void set_suspend(bool p_enabled);
+ void next_frame();
+
+ void set_node_type(int p_type);
+ void set_select_mode(int p_mode);
+
+ void set_selection_visible(bool p_visible);
+
+ void set_camera_override(bool p_enabled);
+ void set_camera_manipulate_mode(EditorDebuggerNode::CameraOverride p_mode);
+
+ void reset_camera_2d_position();
+ void reset_camera_3d_position();
+
+ virtual void setup_session(int p_session_id) override;
+
+ GameViewDebugger() {}
+};
+
+class GameView : public VBoxContainer {
+ GDCLASS(GameView, VBoxContainer);
+
+ enum {
+ CAMERA_RESET_2D,
+ CAMERA_RESET_3D,
+ CAMERA_MODE_INGAME,
+ CAMERA_MODE_EDITORS,
+ };
+
+ Ref<GameViewDebugger> debugger;
+
+ int active_sessions = 0;
+
+ Button *suspend_button = nullptr;
+ Button *next_frame_button = nullptr;
+
+ Button *node_type_button[RuntimeNodeSelect::NODE_TYPE_MAX];
+ Button *select_mode_button[RuntimeNodeSelect::SELECT_MODE_MAX];
+
+ Button *hide_selection = nullptr;
+
+ Button *camera_override_button = nullptr;
+ MenuButton *camera_override_menu = nullptr;
+
+ Panel *panel = nullptr;
+
+ void _sessions_changed();
+
+ void _update_debugger_buttons();
+
+ void _suspend_button_toggled(bool p_pressed);
+
+ void _node_type_pressed(int p_option);
+ void _select_mode_pressed(int p_option);
+
+ void _hide_selection_toggled(bool p_pressed);
+
+ void _camera_override_button_toggled(bool p_pressed);
+ void _camera_override_menu_id_pressed(int p_id);
+
+protected:
+ void _notification(int p_what);
+
+public:
+ void set_state(const Dictionary &p_state);
+ Dictionary get_state() const;
+
+ GameView(Ref<GameViewDebugger> p_debugger);
+};
+
+class GameViewPlugin : public EditorPlugin {
+ GDCLASS(GameViewPlugin, EditorPlugin);
+
+ GameView *game_view = nullptr;
+
+ Ref<GameViewDebugger> debugger;
+
+protected:
+ void _notification(int p_what);
+
+public:
+ virtual String get_name() const override { return "Game"; }
+ bool has_main_screen() const override { return true; }
+ virtual void edit(Object *p_object) override {}
+ virtual bool handles(Object *p_object) const override { return false; }
+ virtual void make_visible(bool p_visible) override;
+
+ virtual void set_state(const Dictionary &p_state) override;
+ virtual Dictionary get_state() const override;
+
+ GameViewPlugin();
+ ~GameViewPlugin();
+};
+
+#endif // GAME_VIEW_PLUGIN_H
diff --git a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp
index 40cb859a4e..2c852c718b 100644
--- a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp
+++ b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp
@@ -540,20 +540,19 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
if (Object::cast_to<ConvexPolygonShape3D>(*s)) {
Vector<Vector3> points = Object::cast_to<ConvexPolygonShape3D>(*s)->get_points();
- if (points.size() > 3) {
+ if (points.size() > 1) { // Need at least 2 points for a line.
Vector<Vector3> varr = Variant(points);
Geometry3D::MeshData md;
Error err = ConvexHullComputer::convex_hull(varr, md);
if (err == OK) {
- Vector<Vector3> points2;
- points2.resize(md.edges.size() * 2);
+ Vector<Vector3> lines;
+ lines.resize(md.edges.size() * 2);
for (uint32_t i = 0; i < md.edges.size(); i++) {
- points2.write[i * 2 + 0] = md.vertices[md.edges[i].vertex_a];
- points2.write[i * 2 + 1] = md.vertices[md.edges[i].vertex_b];
+ lines.write[i * 2 + 0] = md.vertices[md.edges[i].vertex_a];
+ lines.write[i * 2 + 1] = md.vertices[md.edges[i].vertex_b];
}
-
- p_gizmo->add_lines(points2, material);
- p_gizmo->add_collision_segments(points2);
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_collision_segments(lines);
}
}
}
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index c0398fd812..a029a9195f 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -1694,7 +1694,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid()) {
- emit_signal(SNAME("clicked"), this);
+ emit_signal(SNAME("clicked"));
ViewportNavMouseButton orbit_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/orbit_mouse_button").operator int();
ViewportNavMouseButton pan_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/pan_mouse_button").operator int();
@@ -4212,7 +4212,7 @@ Dictionary Node3DEditorViewport::get_state() const {
void Node3DEditorViewport::_bind_methods() {
ADD_SIGNAL(MethodInfo("toggle_maximize_view", PropertyInfo(Variant::OBJECT, "viewport")));
- ADD_SIGNAL(MethodInfo("clicked", PropertyInfo(Variant::OBJECT, "viewport")));
+ ADD_SIGNAL(MethodInfo("clicked"));
}
void Node3DEditorViewport::reset() {
@@ -6576,18 +6576,6 @@ void Node3DEditor::_menu_item_toggled(bool pressed, int p_option) {
tool_option_button[TOOL_OPT_USE_SNAP]->set_pressed(pressed);
snap_enabled = pressed;
} break;
-
- case MENU_TOOL_OVERRIDE_CAMERA: {
- EditorDebuggerNode *const debugger = EditorDebuggerNode::get_singleton();
-
- using Override = EditorDebuggerNode::CameraOverride;
- if (pressed) {
- debugger->set_camera_override((Override)(Override::OVERRIDE_3D_1 + camera_override_viewport_id));
- } else {
- debugger->set_camera_override(Override::OVERRIDE_NONE);
- }
-
- } break;
}
}
@@ -6614,36 +6602,6 @@ void Node3DEditor::_menu_gizmo_toggled(int p_option) {
update_all_gizmos();
}
-void Node3DEditor::_update_camera_override_button(bool p_game_running) {
- Button *const button = tool_option_button[TOOL_OPT_OVERRIDE_CAMERA];
-
- if (p_game_running) {
- button->set_disabled(false);
- button->set_tooltip_text(TTR("Project Camera Override\nOverrides the running project's camera with the editor viewport camera."));
- } else {
- button->set_disabled(true);
- button->set_pressed(false);
- button->set_tooltip_text(TTR("Project Camera Override\nNo project instance running. Run the project from the editor to use this feature."));
- }
-}
-
-void Node3DEditor::_update_camera_override_viewport(Object *p_viewport) {
- Node3DEditorViewport *current_viewport = Object::cast_to<Node3DEditorViewport>(p_viewport);
-
- if (!current_viewport) {
- return;
- }
-
- EditorDebuggerNode *const debugger = EditorDebuggerNode::get_singleton();
-
- camera_override_viewport_id = current_viewport->index;
- if (debugger->get_camera_override() >= EditorDebuggerNode::OVERRIDE_3D_1) {
- using Override = EditorDebuggerNode::CameraOverride;
-
- debugger->set_camera_override((Override)(Override::OVERRIDE_3D_1 + camera_override_viewport_id));
- }
-}
-
void Node3DEditor::_menu_item_pressed(int p_option) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
switch (p_option) {
@@ -6674,6 +6632,9 @@ void Node3DEditor::_menu_item_pressed(int p_option) {
} break;
case MENU_VIEW_USE_1_VIEWPORT: {
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_1_VIEWPORT);
+ if (last_used_viewport > 0) {
+ last_used_viewport = 0;
+ }
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), true);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
@@ -6685,6 +6646,9 @@ void Node3DEditor::_menu_item_pressed(int p_option) {
} break;
case MENU_VIEW_USE_2_VIEWPORTS: {
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_2_VIEWPORTS);
+ if (last_used_viewport > 1) {
+ last_used_viewport = 0;
+ }
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), true);
@@ -6696,6 +6660,9 @@ void Node3DEditor::_menu_item_pressed(int p_option) {
} break;
case MENU_VIEW_USE_2_VIEWPORTS_ALT: {
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_2_VIEWPORTS_ALT);
+ if (last_used_viewport > 1) {
+ last_used_viewport = 0;
+ }
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
@@ -6707,6 +6674,9 @@ void Node3DEditor::_menu_item_pressed(int p_option) {
} break;
case MENU_VIEW_USE_3_VIEWPORTS: {
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_3_VIEWPORTS);
+ if (last_used_viewport > 2) {
+ last_used_viewport = 0;
+ }
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
@@ -6718,6 +6688,9 @@ void Node3DEditor::_menu_item_pressed(int p_option) {
} break;
case MENU_VIEW_USE_3_VIEWPORTS_ALT: {
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_3_VIEWPORTS_ALT);
+ if (last_used_viewport > 2) {
+ last_used_viewport = 0;
+ }
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
@@ -8037,7 +8010,6 @@ void Node3DEditor::_update_theme() {
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_button_icon(get_editor_theme_icon(SNAME("Object")));
tool_option_button[TOOL_OPT_USE_SNAP]->set_button_icon(get_editor_theme_icon(SNAME("Snap")));
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_button_icon(get_editor_theme_icon(SNAME("Camera3D")));
view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), get_editor_theme_icon(SNAME("Panels1")));
view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), get_editor_theme_icon(SNAME("Panels2")));
@@ -8073,9 +8045,6 @@ void Node3DEditor::_notification(int p_what) {
SceneTreeDock::get_singleton()->get_tree_editor()->connect("node_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons));
editor_selection->connect("selection_changed", callable_mp(this, &Node3DEditor::_selection_changed));
- EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button).bind(false));
- EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button).bind(true));
-
_update_preview_environment();
sun_state->set_custom_minimum_size(sun_vb->get_combined_minimum_size());
@@ -8111,15 +8080,6 @@ void Node3DEditor::_notification(int p_what) {
}
} break;
- case NOTIFICATION_VISIBILITY_CHANGED: {
- if (!is_visible() && tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->is_pressed()) {
- EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton();
-
- debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_NONE);
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_pressed(false);
- }
- } break;
-
case NOTIFICATION_PHYSICS_PROCESS: {
if (do_snap_selected_nodes_to_floor) {
_snap_selected_nodes_to_floor();
@@ -8221,6 +8181,10 @@ VSplitContainer *Node3DEditor::get_shader_split() {
return shader_split;
}
+Node3DEditorViewport *Node3DEditor::get_last_used_viewport() {
+ return viewports[last_used_viewport];
+}
+
void Node3DEditor::add_control_to_left_panel(Control *p_control) {
left_panel_split->add_child(p_control);
left_panel_split->move_child(p_control, 0);
@@ -8398,6 +8362,10 @@ void Node3DEditor::_toggle_maximize_view(Object *p_viewport) {
}
}
+void Node3DEditor::_viewport_clicked(int p_viewport_idx) {
+ last_used_viewport = p_viewport_idx;
+}
+
void Node3DEditor::_node_added(Node *p_node) {
if (EditorNode::get_singleton()->get_scene_root()->is_ancestor_of(p_node)) {
if (Object::cast_to<WorldEnvironment>(p_node)) {
@@ -8690,8 +8658,6 @@ Node3DEditor::Node3DEditor() {
snap_key_enabled = false;
tool_mode = TOOL_MODE_SELECT;
- camera_override_viewport_id = 0;
-
// 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.
@@ -8809,16 +8775,6 @@ Node3DEditor::Node3DEditor() {
tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut_context(this);
main_menu_hbox->add_child(memnew(VSeparator));
-
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA] = memnew(Button);
- main_menu_hbox->add_child(tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]);
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_toggle_mode(true);
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_theme_type_variation("FlatButton");
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_disabled(true);
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_OVERRIDE_CAMERA));
- _update_camera_override_button(false);
-
- main_menu_hbox->add_child(memnew(VSeparator));
sun_button = memnew(Button);
sun_button->set_tooltip_text(TTR("Toggle preview sunlight.\nIf a DirectionalLight3D node is added to the scene, preview sunlight is disabled."));
sun_button->set_toggle_mode(true);
@@ -8961,7 +8917,7 @@ Node3DEditor::Node3DEditor() {
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
viewports[i] = memnew(Node3DEditorViewport(this, i));
viewports[i]->connect("toggle_maximize_view", callable_mp(this, &Node3DEditor::_toggle_maximize_view));
- viewports[i]->connect("clicked", callable_mp(this, &Node3DEditor::_update_camera_override_viewport));
+ viewports[i]->connect("clicked", callable_mp(this, &Node3DEditor::_viewport_clicked).bind(i));
viewports[i]->assign_pending_data_pointers(preview_node, &preview_bounds, accept);
viewport_base->add_child(viewports[i]);
}
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index 9a321be01c..fe5537785a 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -624,7 +624,6 @@ public:
enum ToolOptions {
TOOL_OPT_LOCAL_COORDS,
TOOL_OPT_USE_SNAP,
- TOOL_OPT_OVERRIDE_CAMERA,
TOOL_OPT_MAX
};
@@ -634,6 +633,8 @@ private:
Node3DEditorViewportContainer *viewport_base = nullptr;
Node3DEditorViewport *viewports[VIEWPORTS_COUNT];
+ int last_used_viewport = 0;
+
VSplitContainer *shader_split = nullptr;
HSplitContainer *left_panel_split = nullptr;
HSplitContainer *right_panel_split = nullptr;
@@ -706,7 +707,6 @@ private:
MENU_TOOL_LIST_SELECT,
MENU_TOOL_LOCAL_COORDS,
MENU_TOOL_USE_SNAP,
- MENU_TOOL_OVERRIDE_CAMERA,
MENU_TRANSFORM_CONFIGURE_SNAP,
MENU_TRANSFORM_DIALOG,
MENU_VIEW_USE_1_VIEWPORT,
@@ -761,8 +761,6 @@ private:
void _menu_item_pressed(int p_option);
void _menu_item_toggled(bool pressed, int p_option);
void _menu_gizmo_toggled(int p_option);
- void _update_camera_override_button(bool p_game_running);
- void _update_camera_override_viewport(Object *p_viewport);
// Used for secondary menu items which are displayed depending on the currently selected node
// (such as MeshInstance's "Mesh" menu).
PanelContainer *context_toolbar_panel = nullptr;
@@ -773,8 +771,6 @@ private:
void _generate_selection_boxes();
- int camera_override_viewport_id;
-
void _init_indicators();
void _update_gizmos_menu();
void _update_gizmos_menu_theme();
@@ -783,6 +779,7 @@ private:
void _finish_grid();
void _toggle_maximize_view(Object *p_viewport);
+ void _viewport_clicked(int p_viewport_idx);
Node *custom_camera = nullptr;
@@ -970,6 +967,7 @@ public:
ERR_FAIL_INDEX_V(p_idx, static_cast<int>(VIEWPORTS_COUNT), nullptr);
return viewports[p_idx];
}
+ Node3DEditorViewport *get_last_used_viewport();
void add_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin);
void remove_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin);
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index a0e95915a0..67e5d1c510 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -260,7 +260,7 @@ void VisualShaderGraphPlugin::show_port_preview(VisualShader::Type p_type, int p
vbox->add_child(offset);
VisualShaderNodePortPreview *port_preview = memnew(VisualShaderNodePortPreview);
- port_preview->setup(visual_shader, editor->preview_material, visual_shader->get_shader_type(), p_node_id, p_port_id, p_is_valid);
+ port_preview->setup(visual_shader, editor->preview_material, visual_shader->get_shader_type(), links[p_node_id].output_ports[p_port_id].type == VisualShaderNode::PORT_TYPE_VECTOR_4D, p_node_id, p_port_id, p_is_valid);
port_preview->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
vbox->add_child(port_preview);
link.preview_visible = true;
@@ -556,8 +556,8 @@ void VisualShaderGraphPlugin::register_link(VisualShader::Type p_type, int p_id,
links.insert(p_id, { p_type, p_visual_node, p_graph_element, p_visual_node->get_output_port_for_preview() != -1, -1, HashMap<int, InputPort>(), HashMap<int, Port>(), nullptr, nullptr, nullptr, { nullptr, nullptr, nullptr } });
}
-void VisualShaderGraphPlugin::register_output_port(int p_node_id, int p_port, TextureButton *p_button) {
- links[p_node_id].output_ports.insert(p_port, { p_button });
+void VisualShaderGraphPlugin::register_output_port(int p_node_id, int p_port, VisualShaderNode::PortType p_port_type, TextureButton *p_button) {
+ links[p_node_id].output_ports.insert(p_port, { p_port_type, p_button });
}
void VisualShaderGraphPlugin::register_parameter_name(int p_node_id, LineEdit *p_parameter_name) {
@@ -1222,7 +1222,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool
preview->set_texture_pressed(editor->get_editor_theme_icon(SNAME("GuiVisibilityVisible")));
preview->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
- register_output_port(p_id, j, preview);
+ register_output_port(p_id, j, port_right, preview);
preview->connect(SceneStringName(pressed), callable_mp(editor, &VisualShaderEditor::_preview_select_port).bind(p_id, j), CONNECT_DEFERRED);
hb->add_child(preview);
@@ -8033,7 +8033,15 @@ void VisualShaderNodePortPreview::_shader_changed() {
set_material(mat);
}
-void VisualShaderNodePortPreview::setup(const Ref<VisualShader> &p_shader, Ref<ShaderMaterial> &p_preview_material, VisualShader::Type p_type, int p_node, int p_port, bool p_is_valid) {
+void VisualShaderNodePortPreview::setup(const Ref<VisualShader> &p_shader, Ref<ShaderMaterial> &p_preview_material, VisualShader::Type p_type, bool p_has_transparency, int p_node, int p_port, bool p_is_valid) {
+ if (p_has_transparency) {
+ checkerboard = memnew(TextureRect);
+ checkerboard->set_stretch_mode(TextureRect::STRETCH_TILE);
+ checkerboard->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
+ checkerboard->set_draw_behind_parent(true);
+ add_child(checkerboard);
+ }
+
shader = p_shader;
shader->connect_changed(callable_mp(this, &VisualShaderNodePortPreview::_shader_changed), CONNECT_DEFERRED);
preview_mat = p_preview_material;
@@ -8052,6 +8060,11 @@ Size2 VisualShaderNodePortPreview::get_minimum_size() const {
void VisualShaderNodePortPreview::_notification(int p_what) {
switch (p_what) {
+ case NOTIFICATION_THEME_CHANGED: {
+ if (checkerboard != nullptr) {
+ checkerboard->set_texture(get_theme_icon(SNAME("GuiMiniCheckerboard"), EditorStringName(EditorIcons)));
+ }
+ } break;
case NOTIFICATION_DRAW: {
Vector<Vector2> points = {
Vector2(),
diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h
index bf52e585c1..1877bd094e 100644
--- a/editor/plugins/visual_shader_editor_plugin.h
+++ b/editor/plugins/visual_shader_editor_plugin.h
@@ -109,6 +109,7 @@ private:
};
struct Port {
+ VisualShaderNode::PortType type = VisualShaderNode::PORT_TYPE_SCALAR;
TextureButton *preview_button = nullptr;
};
@@ -143,7 +144,7 @@ public:
void register_shader(VisualShader *p_visual_shader);
void set_connections(const List<VisualShader::Connection> &p_connections);
void register_link(VisualShader::Type p_type, int p_id, VisualShaderNode *p_visual_node, GraphElement *p_graph_element);
- void register_output_port(int p_id, int p_port, TextureButton *p_button);
+ void register_output_port(int p_id, int p_port, VisualShaderNode::PortType p_port_type, TextureButton *p_button);
void register_parameter_name(int p_id, LineEdit *p_parameter_name);
void register_default_input_button(int p_node_id, int p_port_id, Button *p_button);
void register_expression_edit(int p_node_id, CodeEdit *p_expression_edit);
@@ -682,6 +683,7 @@ public:
class VisualShaderNodePortPreview : public Control {
GDCLASS(VisualShaderNodePortPreview, Control);
+ TextureRect *checkerboard = nullptr;
Ref<VisualShader> shader;
Ref<ShaderMaterial> preview_mat;
VisualShader::Type type = VisualShader::Type::TYPE_MAX;
@@ -694,7 +696,7 @@ protected:
public:
virtual Size2 get_minimum_size() const override;
- void setup(const Ref<VisualShader> &p_shader, Ref<ShaderMaterial> &p_preview_material, VisualShader::Type p_type, int p_node, int p_port, bool p_is_valid);
+ void setup(const Ref<VisualShader> &p_shader, Ref<ShaderMaterial> &p_preview_material, VisualShader::Type p_type, bool p_has_transparency, int p_node, int p_port, bool p_is_valid);
};
class VisualShaderConversionPlugin : public EditorResourceConversionPlugin {
diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp
index 733e87b8da..c052974351 100644
--- a/editor/themes/editor_theme_manager.cpp
+++ b/editor/themes/editor_theme_manager.cpp
@@ -1868,6 +1868,12 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme
p_theme->set_stylebox("ScriptEditorPanelFloating", EditorStringName(EditorStyles), make_empty_stylebox(0, 0, 0, 0));
p_theme->set_stylebox("ScriptEditor", EditorStringName(EditorStyles), make_empty_stylebox(0, 0, 0, 0));
+ // Game view.
+ p_theme->set_type_variation("GamePanel", "Panel");
+ Ref<StyleBoxFlat> game_panel = p_theme->get_stylebox(SNAME("panel"), SNAME("Panel"))->duplicate();
+ game_panel->set_corner_radius_all(0);
+ p_theme->set_stylebox(SceneStringName(panel), "GamePanel", game_panel);
+
// Main menu.
Ref<StyleBoxFlat> menu_transparent_style = p_config.button_style->duplicate();
menu_transparent_style->set_bg_color(Color(1, 1, 1, 0));
diff --git a/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected
index 9e169d474d..3770664115 100644
--- a/misc/extension_api_validation/4.3-stable.expected
+++ b/misc/extension_api_validation/4.3-stable.expected
@@ -7,7 +7,6 @@ should instead be used to justify these changes and describe how users should wo
Add new entries at the end of the file.
## Changes between 4.3-stable and 4.4-stable
-
GH-95374
--------
Validate extension JSON: Error: Field 'classes/ShapeCast2D/properties/collision_result': getter changed value in new API, from "_get_collision_result" to &"get_collision_result".
@@ -102,3 +101,10 @@ GH-97020
Validate extension JSON: Error: Field 'classes/AnimationNode/methods/_process': is_const changed value in new API, from true to false.
`_process` virtual method fixed to be non const instead.
+
+
+GH-97257
+--------
+Validate extension JSON: Error: Field 'classes/EditorFeatureProfile/enums/Feature/values/FEATURE_MAX': value changed value in new API, from 8.0 to 9.
+
+New entry to the `EditorFeatureProfile.Feature` enum added. Those need to go before `FEATURE_MAX`, which will always cause a compatibility break.
diff --git a/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out
index 94e2ec2af8..fb616f1e94 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out
+++ b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out
@@ -1,2 +1,2 @@
GDTEST_OK
-0
+0.0
diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
index 5318d11f33..e91c7386fe 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
@@ -7,17 +7,17 @@ const const_packed_ints: PackedFloat64Array = [52]
func test():
Utils.check(typeof(const_float_int) == TYPE_FLOAT)
- Utils.check(str(const_float_int) == '19')
+ Utils.check(str(const_float_int) == '19.0')
Utils.check(typeof(const_float_plus) == TYPE_FLOAT)
- Utils.check(str(const_float_plus) == '34')
+ Utils.check(str(const_float_plus) == '34.0')
Utils.check(typeof(const_float_cast) == TYPE_FLOAT)
- Utils.check(str(const_float_cast) == '76')
+ Utils.check(str(const_float_cast) == '76.0')
Utils.check(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY)
Utils.check(str(const_packed_empty) == '[]')
Utils.check(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY)
- Utils.check(str(const_packed_ints) == '[52]')
+ Utils.check(str(const_packed_ints) == '[52.0]')
Utils.check(typeof(const_packed_ints[0]) == TYPE_FLOAT)
- Utils.check(str(const_packed_ints[0]) == '52')
+ Utils.check(str(const_packed_ints[0]) == '52.0')
print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out b/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out
index 15666c46ad..abf11548cb 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out
+++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out
@@ -1,2 +1,2 @@
GDTEST_OK
-4
+4.0
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
index fe0274c27b..eb53d0a700 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
@@ -54,39 +54,39 @@ func test():
untyped_basic.push_back(430.0)
inferred_basic.push_back(263.0)
typed_basic.push_back(518.0)
- Utils.check(str(empty_floats) == '[705, 430, 263, 518]')
- Utils.check(str(untyped_basic) == '[705, 430, 263, 518]')
- Utils.check(str(inferred_basic) == '[705, 430, 263, 518]')
- Utils.check(str(typed_basic) == '[705, 430, 263, 518]')
+ Utils.check(str(empty_floats) == '[705.0, 430.0, 263.0, 518.0]')
+ Utils.check(str(untyped_basic) == '[705.0, 430.0, 263.0, 518.0]')
+ Utils.check(str(inferred_basic) == '[705.0, 430.0, 263.0, 518.0]')
+ Utils.check(str(typed_basic) == '[705.0, 430.0, 263.0, 518.0]')
const constant_float := 950.0
const constant_int := 170
var typed_float := 954.0
var filled_floats: Array[float] = [constant_float, constant_int, typed_float, empty_floats[1] + empty_floats[2]]
- Utils.check(str(filled_floats) == '[950, 170, 954, 693]')
+ Utils.check(str(filled_floats) == '[950.0, 170.0, 954.0, 693.0]')
Utils.check(filled_floats.get_typed_builtin() == TYPE_FLOAT)
var casted_floats := [empty_floats[2] * 2] as Array[float]
- Utils.check(str(casted_floats) == '[526]')
+ Utils.check(str(casted_floats) == '[526.0]')
Utils.check(casted_floats.get_typed_builtin() == TYPE_FLOAT)
var returned_floats = (func () -> Array[float]: return [554]).call()
- Utils.check(str(returned_floats) == '[554]')
+ Utils.check(str(returned_floats) == '[554.0]')
Utils.check(returned_floats.get_typed_builtin() == TYPE_FLOAT)
var passed_floats = floats_identity([663.0 if randf() > 0.5 else 663.0])
- Utils.check(str(passed_floats) == '[663]')
+ Utils.check(str(passed_floats) == '[663.0]')
Utils.check(passed_floats.get_typed_builtin() == TYPE_FLOAT)
var default_floats = (func (floats: Array[float] = [364.0]): return floats).call()
- Utils.check(str(default_floats) == '[364]')
+ Utils.check(str(default_floats) == '[364.0]')
Utils.check(default_floats.get_typed_builtin() == TYPE_FLOAT)
var typed_int := 556
var converted_floats: Array[float] = [typed_int]
converted_floats.push_back(498)
- Utils.check(str(converted_floats) == '[556, 498]')
+ Utils.check(str(converted_floats) == '[556.0, 498.0]')
Utils.check(converted_floats.get_typed_builtin() == TYPE_FLOAT)
@@ -95,7 +95,7 @@ func test():
Utils.check(constant_basic.get_typed_builtin() == TYPE_NIL)
const constant_floats: Array[float] = [constant_float - constant_basic[0] - constant_int]
- Utils.check(str(constant_floats) == '[552]')
+ Utils.check(str(constant_floats) == '[552.0]')
Utils.check(constant_floats.get_typed_builtin() == TYPE_FLOAT)
@@ -103,15 +103,15 @@ func test():
untyped_basic = source_floats
var destination_floats: Array[float] = untyped_basic
destination_floats[0] -= 0.74
- Utils.check(str(source_floats) == '[999]')
- Utils.check(str(untyped_basic) == '[999]')
- Utils.check(str(destination_floats) == '[999]')
+ Utils.check(str(source_floats) == '[999.0]')
+ Utils.check(str(untyped_basic) == '[999.0]')
+ Utils.check(str(destination_floats) == '[999.0]')
Utils.check(destination_floats.get_typed_builtin() == TYPE_FLOAT)
var duplicated_floats := empty_floats.duplicate().slice(2, 3)
duplicated_floats[0] *= 3
- Utils.check(str(duplicated_floats) == '[789]')
+ Utils.check(str(duplicated_floats) == '[789.0]')
Utils.check(duplicated_floats.get_typed_builtin() == TYPE_FLOAT)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
index 9d3fffd1de..c9ab368f45 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
@@ -62,44 +62,44 @@ func test():
untyped_basic[430.0] = 34.0
inferred_basic[263.0] = 362.0
typed_basic[518.0] = 815.0
- Utils.check(str(empty_floats) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
- Utils.check(str(untyped_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
- Utils.check(str(inferred_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
- Utils.check(str(typed_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
+ Utils.check(str(empty_floats) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }')
+ Utils.check(str(untyped_basic) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }')
+ Utils.check(str(inferred_basic) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }')
+ Utils.check(str(typed_basic) == '{ 705.0: 507.0, 430.0: 34.0, 263.0: 362.0, 518.0: 815.0 }')
const constant_float := 950.0
const constant_int := 170
var typed_float := 954.0
var filled_floats: Dictionary[float, float] = { constant_float: constant_int, typed_float: empty_floats[430.0] + empty_floats[263.0] }
- Utils.check(str(filled_floats) == '{ 950: 170, 954: 396 }')
+ Utils.check(str(filled_floats) == '{ 950.0: 170.0, 954.0: 396.0 }')
Utils.check(filled_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(filled_floats.get_typed_value_builtin() == TYPE_FLOAT)
var casted_floats := { empty_floats[263.0] * 2: empty_floats[263.0] / 2 } as Dictionary[float, float]
- Utils.check(str(casted_floats) == '{ 724: 181 }')
+ Utils.check(str(casted_floats) == '{ 724.0: 181.0 }')
Utils.check(casted_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(casted_floats.get_typed_value_builtin() == TYPE_FLOAT)
var returned_floats = (func () -> Dictionary[float, float]: return { 554: 455 }).call()
- Utils.check(str(returned_floats) == '{ 554: 455 }')
+ Utils.check(str(returned_floats) == '{ 554.0: 455.0 }')
Utils.check(returned_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(returned_floats.get_typed_value_builtin() == TYPE_FLOAT)
var passed_floats = floats_identity({ 663.0 if randf() > 0.5 else 663.0: 366.0 if randf() <= 0.5 else 366.0 })
- Utils.check(str(passed_floats) == '{ 663: 366 }')
+ Utils.check(str(passed_floats) == '{ 663.0: 366.0 }')
Utils.check(passed_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(passed_floats.get_typed_value_builtin() == TYPE_FLOAT)
var default_floats = (func (floats: Dictionary[float, float] = { 364.0: 463.0 }): return floats).call()
- Utils.check(str(default_floats) == '{ 364: 463 }')
+ Utils.check(str(default_floats) == '{ 364.0: 463.0 }')
Utils.check(default_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(default_floats.get_typed_value_builtin() == TYPE_FLOAT)
var typed_int := 556
var converted_floats: Dictionary[float, float] = { typed_int: typed_int }
converted_floats[498.0] = 894
- Utils.check(str(converted_floats) == '{ 556: 556, 498: 894 }')
+ Utils.check(str(converted_floats) == '{ 556.0: 556.0, 498.0: 894.0 }')
Utils.check(converted_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(converted_floats.get_typed_value_builtin() == TYPE_FLOAT)
@@ -110,7 +110,7 @@ func test():
Utils.check(constant_basic.get_typed_value_builtin() == TYPE_NIL)
const constant_floats: Dictionary[float, float] = { constant_float - constant_basic[228] - constant_int: constant_float + constant_basic[228] + constant_int }
- Utils.check(str(constant_floats) == '{ -42: 1942 }')
+ Utils.check(str(constant_floats) == '{ -42.0: 1942.0 }')
Utils.check(constant_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(constant_floats.get_typed_value_builtin() == TYPE_FLOAT)
@@ -119,9 +119,9 @@ func test():
untyped_basic = source_floats
var destination_floats: Dictionary[float, float] = untyped_basic
destination_floats[999.74] -= 0.999
- Utils.check(str(source_floats) == '{ 999.74: 47 }')
- Utils.check(str(untyped_basic) == '{ 999.74: 47 }')
- Utils.check(str(destination_floats) == '{ 999.74: 47 }')
+ Utils.check(str(source_floats) == '{ 999.74: 47.0 }')
+ Utils.check(str(untyped_basic) == '{ 999.74: 47.0 }')
+ Utils.check(str(destination_floats) == '{ 999.74: 47.0 }')
Utils.check(destination_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(destination_floats.get_typed_value_builtin() == TYPE_FLOAT)
@@ -131,7 +131,7 @@ func test():
duplicated_floats.erase(430.0)
duplicated_floats.erase(518.0)
duplicated_floats[263.0] *= 3
- Utils.check(str(duplicated_floats) == '{ 263: 1086 }')
+ Utils.check(str(duplicated_floats) == '{ 263.0: 1086.0 }')
Utils.check(duplicated_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(duplicated_floats.get_typed_value_builtin() == TYPE_FLOAT)
diff --git a/modules/gdscript/tests/scripts/parser/features/export_arrays.out b/modules/gdscript/tests/scripts/parser/features/export_arrays.out
index f1522d096f..7201d8082d 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_arrays.out
+++ b/modules/gdscript/tests/scripts/parser/features/export_arrays.out
@@ -80,21 +80,21 @@ var test_placeholder: Array
var test_placeholder_packed: PackedStringArray
hint=TYPE_STRING hint_string="<String>/<PLACEHOLDER_TEXT>:Placeholder" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int: Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int_packed_byte: PackedByteArray
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int_packed32: PackedInt32Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int_packed64: PackedInt64Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_int_float_step: Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10,0.01" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0,0.01" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_float: Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_float_packed32: PackedFloat32Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_float_packed64: PackedFloat64Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_exp_easing: Array
hint=TYPE_STRING hint_string="<float>/<EXP_EASING>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_exp_easing_packed32: PackedFloat32Array
@@ -126,14 +126,14 @@ var test_weak_packed_vector3_array: PackedVector3Array
var test_weak_packed_vector4_array: PackedVector4Array
hint=TYPE_STRING hint_string="<Vector4>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_byte_array: PackedByteArray
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_int32_array: PackedInt32Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_int64_array: PackedInt64Array
- hint=TYPE_STRING hint_string="<int>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<int>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_float32_array: PackedFloat32Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_weak_packed_float64_array: PackedFloat64Array
- hint=TYPE_STRING hint_string="<float>/<RANGE>:1,10" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=TYPE_STRING hint_string="<float>/<RANGE>:1.0,10.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_noalpha_weak_packed_color_array: PackedColorArray
hint=TYPE_STRING hint_string="<Color>/<COLOR_NO_ALPHA>:" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out
index 0d915e00e6..c0bf4d6e06 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.out
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out
@@ -4,11 +4,11 @@ var test_weak_int: int = 1
var test_hard_int: int = 2
hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range: int = 100
- hint=RANGE hint_string="0,100" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=RANGE hint_string="0.0,100.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_step: int = 101
- hint=RANGE hint_string="0,100,1" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=RANGE hint_string="0.0,100.0,1.0" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_range_step_or_greater: int = 102
- hint=RANGE hint_string="0,100,1,or_greater" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
+ hint=RANGE hint_string="0.0,100.0,1.0,or_greater" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_color: Color = Color(0, 0, 0, 1)
hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE class_name=&""
var test_color_no_alpha: Color = Color(0, 0, 0, 1)
diff --git a/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out
index c5958365ec..d94cbe5556 100644
--- a/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out
+++ b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out
@@ -13,4 +13,4 @@ true
0
-255
256
-2
+2.0
diff --git a/modules/gdscript/tests/scripts/parser/features/number_separators.out b/modules/gdscript/tests/scripts/parser/features/number_separators.out
index b0d2fd94fe..9407af9cd8 100644
--- a/modules/gdscript/tests/scripts/parser/features/number_separators.out
+++ b/modules/gdscript/tests/scripts/parser/features/number_separators.out
@@ -13,12 +13,12 @@ GDTEST_OK
---
-1234.4567
-1234.4567
--1234
--1234
+-1234.0
+-1234.0
0.4567
0.4567
---
--1234500
--1234500
--1234500
--1234500
+-1234500.0
+-1234500.0
+-1234500.0
+-1234500.0
diff --git a/modules/gdscript/tests/scripts/parser/features/operator_assign.out b/modules/gdscript/tests/scripts/parser/features/operator_assign.out
index b0cb63ef59..29910adf38 100644
--- a/modules/gdscript/tests/scripts/parser/features/operator_assign.out
+++ b/modules/gdscript/tests/scripts/parser/features/operator_assign.out
@@ -1,2 +1,2 @@
GDTEST_OK
-8
+8.0
diff --git a/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out b/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out
index 22929bf636..04b0773991 100644
--- a/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out
+++ b/modules/gdscript/tests/scripts/runtime/features/chain_assignment_works.out
@@ -1,7 +1,7 @@
GDTEST_OK
-{ 1: (2, 0) }
-{ 3: (4, 0) }
-[[(5, 0)]]
-[[(6, 0)]]
-[[(7, 0)]]
-[X: (8, 9, 7), Y: (0, 1, 0), Z: (0, 0, 1), O: (0, 0, 0)]
+{ 1: (2.0, 0.0) }
+{ 3: (4.0, 0.0) }
+[[(5.0, 0.0)]]
+[[(6.0, 0.0)]]
+[[(7.0, 0.0)]]
+[X: (8.0, 9.0, 7.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0), O: (0.0, 0.0, 0.0)]
diff --git a/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out
index a9ef4919cf..78ea2a2d80 100644
--- a/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out
+++ b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out
@@ -1,8 +1,8 @@
GDTEST_OK
-x is 1
+x is 1.0
typeof x is 3
-x is 2
+x is 2.0
typeof x is 3
-x is 3
+x is 3.0
typeof x is 3
ok
diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out
index 5b981bc8bb..5492c8f763 100644
--- a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out
+++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out
@@ -11,7 +11,7 @@ a
1
b
1
-(1, 1)
-(0, 0)
-(6, 1)
-(0, 0)
+(1.0, 1.0)
+(0.0, 0.0)
+(6.0, 1.0)
+(0.0, 0.0)
diff --git a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out
index c51759f481..e82e31bbed 100644
--- a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out
+++ b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out
@@ -1,26 +1,26 @@
GDTEST_OK
===
-prop1 setter (0, 0)
-prop1 setter (1, 0)
+prop1 setter (0.0, 0.0)
+prop1 setter (1.0, 0.0)
---
prop1 setter <Inner>
subprop getter
-subprop setter (1, 0)
+subprop setter (1.0, 0.0)
===
prop2 setter <Inner>
subprop getter
-subprop setter (1, 0)
+subprop setter (1.0, 0.0)
===
-prop3 setter (0, 0)
+prop3 setter (0.0, 0.0)
prop3 getter
-prop3 setter (1, 0)
+prop3 setter (1.0, 0.0)
---
prop3 setter <Inner>
prop3 getter
subprop getter
-subprop setter (1, 0)
+subprop setter (1.0, 0.0)
===
prop4 setter <Inner>
prop4 getter
subprop getter
-subprop setter (1, 0)
+subprop setter (1.0, 0.0)
diff --git a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out
index 31b3b3a3a8..8617a65c33 100644
--- a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out
+++ b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out
@@ -1,4 +1,4 @@
GDTEST_OK
-setting vec from (0, 0) to (2, 0)
-setting vec from (0, 0) to (0, 2)
-vec is (0, 0)
+setting vec from (0.0, 0.0) to (2.0, 0.0)
+setting vec from (0.0, 0.0) to (0.0, 2.0)
+vec is (0.0, 0.0)
diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.out b/modules/gdscript/tests/scripts/runtime/features/stringify.out
index 7833b6e213..2463d70ef4 100644
--- a/modules/gdscript/tests/scripts/runtime/features/stringify.out
+++ b/modules/gdscript/tests/scripts/runtime/features/stringify.out
@@ -9,13 +9,13 @@ hello world
[P: (0, 0), S: (0, 0)]
(0.25, 0.25, 0.25)
(0, 0, 0)
-[X: (1, 0), Y: (0, 1), O: (0, 0)]
-[N: (1, 2, 3), D: 4]
-(1, 2, 3, 4)
-[P: (0, 0, 0), S: (1, 1, 1)]
-[X: (1, 0, 0), Y: (0, 1, 0), Z: (0, 0, 1)]
-[X: (1, 0, 0), Y: (0, 1, 0), Z: (0, 0, 1), O: (0, 0, 0)]
+[X: (1.0, 0.0), Y: (0.0, 1.0), O: (0.0, 0.0)]
+[N: (1.0, 2.0, 3.0), D: 4]
(1, 2, 3, 4)
+[P: (0.0, 0.0, 0.0), S: (1.0, 1.0, 1.0)]
+[X: (1.0, 0.0, 0.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0)]
+[X: (1.0, 0.0, 0.0), Y: (0.0, 1.0, 0.0), Z: (0.0, 0.0, 1.0), O: (0.0, 0.0, 0.0)]
+(1.0, 2.0, 3.0, 4.0)
hello
hello/world
RID(0)
@@ -26,10 +26,10 @@ Node::[signal]property_list_changed
[255, 0, 1]
[-1, 0, 1]
[-1, 0, 1]
-[-1, 0, 1]
-[-1, 0, 1]
+[-1.0, 0.0, 1.0]
+[-1.0, 0.0, 1.0]
["hello", "world"]
-[(1, 1), (0, 0)]
-[(1, 1, 1), (0, 0, 0)]
-[(1, 0, 0, 1), (0, 0, 1, 1), (0, 1, 0, 1)]
+[(1.0, 1.0), (0.0, 0.0)]
+[(1.0, 1.0, 1.0), (0.0, 0.0, 0.0)]
+[(1.0, 0.0, 0.0, 1.0), (0.0, 0.0, 1.0, 1.0), (0.0, 1.0, 0.0, 1.0)]
[(1, 1, 1, 1), (0, 0, 0, 0)]
diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd
index fa289e442f..225bcb3008 100644
--- a/modules/gdscript/tests/scripts/utils.notest.gd
+++ b/modules/gdscript/tests/scripts/utils.notest.gd
@@ -1,6 +1,5 @@
class_name Utils
-
# `assert()` is not evaluated in non-debug builds. Do not use `assert()`
# for anything other than testing the `assert()` itself.
static func check(condition: Variant) -> void:
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 7b6b5461ab..400ff0a3b8 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -4374,9 +4374,45 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
return true;
}
+static String _get_vector2_cs_ctor_args(const Vector2 &p_vec2) {
+ return String::num_real(p_vec2.x, true) + "f, " +
+ String::num_real(p_vec2.y, true) + "f";
+}
+
+static String _get_vector3_cs_ctor_args(const Vector3 &p_vec3) {
+ return String::num_real(p_vec3.x, true) + "f, " +
+ String::num_real(p_vec3.y, true) + "f, " +
+ String::num_real(p_vec3.z, true) + "f";
+}
+
+static String _get_vector4_cs_ctor_args(const Vector4 &p_vec4) {
+ return String::num_real(p_vec4.x, true) + "f, " +
+ String::num_real(p_vec4.y, true) + "f, " +
+ String::num_real(p_vec4.z, true) + "f, " +
+ String::num_real(p_vec4.w, true) + "f";
+}
+
+static String _get_vector2i_cs_ctor_args(const Vector2i &p_vec2i) {
+ return itos(p_vec2i.x) + ", " + itos(p_vec2i.y);
+}
+
+static String _get_vector3i_cs_ctor_args(const Vector3i &p_vec3i) {
+ return itos(p_vec3i.x) + ", " + itos(p_vec3i.y) + ", " + itos(p_vec3i.z);
+}
+
+static String _get_vector4i_cs_ctor_args(const Vector4i &p_vec4i) {
+ return itos(p_vec4i.x) + ", " + itos(p_vec4i.y) + ", " + itos(p_vec4i.z) + ", " + itos(p_vec4i.w);
+}
+
+static String _get_color_cs_ctor_args(const Color &p_color) {
+ return String::num(p_color.r, 4) + "f, " +
+ String::num(p_color.g, 4) + "f, " +
+ String::num(p_color.b, 4) + "f, " +
+ String::num(p_color.a, 4) + "f";
+}
+
bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, ArgumentInterface &r_iarg) {
r_iarg.def_param_value = p_val;
- r_iarg.default_argument = p_val.operator String();
switch (p_val.get_type()) {
case Variant::NIL:
@@ -4389,10 +4425,14 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
break;
case Variant::INT:
if (r_iarg.type.cname != name_cache.type_int) {
- r_iarg.default_argument = "(%s)(" + r_iarg.default_argument + ")";
+ r_iarg.default_argument = "(%s)(" + p_val.operator String() + ")";
+ } else {
+ r_iarg.default_argument = p_val.operator String();
}
break;
case Variant::FLOAT:
+ r_iarg.default_argument = p_val.operator String();
+
if (r_iarg.type.cname == name_cache.type_float) {
r_iarg.default_argument += "f";
}
@@ -4402,7 +4442,7 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
case Variant::NODE_PATH:
if (r_iarg.type.cname == name_cache.type_StringName || r_iarg.type.cname == name_cache.type_NodePath) {
if (r_iarg.default_argument.length() > 0) {
- r_iarg.default_argument = "(%s)\"" + r_iarg.default_argument + "\"";
+ r_iarg.default_argument = "(%s)\"" + p_val.operator String() + "\"";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;
} else {
// No need for a special `in` statement to change `null` to `""`. Marshaling takes care of this already.
@@ -4410,40 +4450,62 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
}
} else {
CRASH_COND(r_iarg.type.cname != name_cache.type_String);
- r_iarg.default_argument = "\"" + r_iarg.default_argument + "\"";
+ r_iarg.default_argument = "\"" + p_val.operator String() + "\"";
}
break;
case Variant::PLANE: {
Plane plane = p_val.operator Plane();
- r_iarg.default_argument = "new Plane(new Vector3" + plane.normal.operator String() + ", " + rtos(plane.d) + ")";
+ r_iarg.default_argument = "new Plane(new Vector3(" +
+ _get_vector3_cs_ctor_args(plane.normal) + "), " + rtos(plane.d) + "f)";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
case Variant::AABB: {
AABB aabb = p_val.operator ::AABB();
- r_iarg.default_argument = "new Aabb(new Vector3" + aabb.position.operator String() + ", new Vector3" + aabb.size.operator String() + ")";
+ r_iarg.default_argument = "new Aabb(new Vector3(" +
+ _get_vector3_cs_ctor_args(aabb.position) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(aabb.size) + "))";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
case Variant::RECT2: {
Rect2 rect = p_val.operator Rect2();
- r_iarg.default_argument = "new Rect2(new Vector2" + rect.position.operator String() + ", new Vector2" + rect.size.operator String() + ")";
+ r_iarg.default_argument = "new Rect2(new Vector2(" +
+ _get_vector2_cs_ctor_args(rect.position) + "), new Vector2(" +
+ _get_vector2_cs_ctor_args(rect.size) + "))";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
case Variant::RECT2I: {
Rect2i rect = p_val.operator Rect2i();
- r_iarg.default_argument = "new Rect2I(new Vector2I" + rect.position.operator String() + ", new Vector2I" + rect.size.operator String() + ")";
+ r_iarg.default_argument = "new Rect2I(new Vector2I(" +
+ _get_vector2i_cs_ctor_args(rect.position) + "), new Vector2I(" +
+ _get_vector2i_cs_ctor_args(rect.size) + "))";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
case Variant::COLOR:
+ r_iarg.default_argument = "new Color(" + _get_color_cs_ctor_args(p_val.operator Color()) + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ break;
case Variant::VECTOR2:
+ r_iarg.default_argument = "new Vector2(" + _get_vector2_cs_ctor_args(p_val.operator Vector2()) + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ break;
case Variant::VECTOR2I:
+ r_iarg.default_argument = "new Vector2I(" + _get_vector2i_cs_ctor_args(p_val.operator Vector2i()) + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ break;
case Variant::VECTOR3:
+ r_iarg.default_argument = "new Vector3(" + _get_vector3_cs_ctor_args(p_val.operator Vector3()) + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ break;
case Variant::VECTOR3I:
- r_iarg.default_argument = "new %s" + r_iarg.default_argument;
+ r_iarg.default_argument = "new Vector3I(" + _get_vector3i_cs_ctor_args(p_val.operator Vector3i()) + ")";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
break;
case Variant::VECTOR4:
+ r_iarg.default_argument = "new Vector4(" + _get_vector4_cs_ctor_args(p_val.operator Vector4()) + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ break;
case Variant::VECTOR4I:
- r_iarg.default_argument = "new %s" + r_iarg.default_argument;
+ r_iarg.default_argument = "new Vector4I(" + _get_vector4i_cs_ctor_args(p_val.operator Vector4i()) + ")";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
break;
case Variant::OBJECT:
@@ -4493,7 +4555,10 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
if (transform == Transform2D()) {
r_iarg.default_argument = "Transform2D.Identity";
} else {
- r_iarg.default_argument = "new Transform2D(new Vector2" + transform.columns[0].operator String() + ", new Vector2" + transform.columns[1].operator String() + ", new Vector2" + transform.columns[2].operator String() + ")";
+ r_iarg.default_argument = "new Transform2D(new Vector2(" +
+ _get_vector2_cs_ctor_args(transform.columns[0]) + "), new Vector2(" +
+ _get_vector2_cs_ctor_args(transform.columns[1]) + "), new Vector2(" +
+ _get_vector2_cs_ctor_args(transform.columns[2]) + "))";
}
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
@@ -4503,7 +4568,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
r_iarg.default_argument = "Transform3D.Identity";
} else {
Basis basis = transform.basis;
- r_iarg.default_argument = "new Transform3D(new Vector3" + basis.get_column(0).operator String() + ", new Vector3" + basis.get_column(1).operator String() + ", new Vector3" + basis.get_column(2).operator String() + ", new Vector3" + transform.origin.operator String() + ")";
+ r_iarg.default_argument = "new Transform3D(new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(0)) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(1)) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(2)) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(transform.origin) + "))";
}
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
@@ -4512,7 +4581,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
if (projection == Projection()) {
r_iarg.default_argument = "Projection.Identity";
} else {
- r_iarg.default_argument = "new Projection(new Vector4" + projection.columns[0].operator String() + ", new Vector4" + projection.columns[1].operator String() + ", new Vector4" + projection.columns[2].operator String() + ", new Vector4" + projection.columns[3].operator String() + ")";
+ r_iarg.default_argument = "new Projection(new Vector4(" +
+ _get_vector4_cs_ctor_args(projection.columns[0]) + "), new Vector4(" +
+ _get_vector4_cs_ctor_args(projection.columns[1]) + "), new Vector4(" +
+ _get_vector4_cs_ctor_args(projection.columns[2]) + "), new Vector4(" +
+ _get_vector4_cs_ctor_args(projection.columns[3]) + "))";
}
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
@@ -4521,7 +4594,10 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
if (basis == Basis()) {
r_iarg.default_argument = "Basis.Identity";
} else {
- r_iarg.default_argument = "new Basis(new Vector3" + basis.get_column(0).operator String() + ", new Vector3" + basis.get_column(1).operator String() + ", new Vector3" + basis.get_column(2).operator String() + ")";
+ r_iarg.default_argument = "new Basis(new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(0)) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(1)) + "), new Vector3(" +
+ _get_vector3_cs_ctor_args(basis.get_column(2)) + "))";
}
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
@@ -4530,7 +4606,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
if (quaternion == Quaternion()) {
r_iarg.default_argument = "Quaternion.Identity";
} else {
- r_iarg.default_argument = "new Quaternion" + quaternion.operator String();
+ r_iarg.default_argument = "new Quaternion(" +
+ String::num_real(quaternion.x, false) + "f, " +
+ String::num_real(quaternion.y, false) + "f, " +
+ String::num_real(quaternion.z, false) + "f, " +
+ String::num_real(quaternion.w, false) + "f)";
}
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
} break;
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index ba2c82d7f0..492c3f3426 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -74,7 +74,8 @@ bool DisplayServerAndroid::has_feature(Feature p_feature) const {
//case FEATURE_MOUSE_WARP:
//case FEATURE_NATIVE_DIALOG:
case FEATURE_NATIVE_DIALOG_INPUT:
- //case FEATURE_NATIVE_DIALOG_FILE:
+ case FEATURE_NATIVE_DIALOG_FILE:
+ //case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
//case FEATURE_NATIVE_ICON:
//case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_CLIPBOARD:
@@ -191,6 +192,19 @@ void DisplayServerAndroid::emit_input_dialog_callback(String p_text) {
}
}
+Error DisplayServerAndroid::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+ GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
+ ERR_FAIL_NULL_V(godot_java, FAILED);
+ file_picker_callback = p_callback;
+ return godot_java->show_file_picker(p_current_directory, p_filename, p_mode, p_filters);
+}
+
+void DisplayServerAndroid::emit_file_picker_callback(bool p_ok, const Vector<String> &p_selected_paths) {
+ if (file_picker_callback.is_valid()) {
+ file_picker_callback.call_deferred(p_ok, p_selected_paths, 0);
+ }
+}
+
TypedArray<Rect2> DisplayServerAndroid::get_display_cutouts() const {
GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
ERR_FAIL_NULL_V(godot_io_java, Array());
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index cab1b503f7..98d401432f 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -90,6 +90,7 @@ class DisplayServerAndroid : public DisplayServer {
Callable system_theme_changed;
Callable input_dialog_callback;
+ Callable file_picker_callback;
void _window_callback(const Callable &p_callable, const Variant &p_arg, bool p_deferred = false) const;
@@ -123,6 +124,9 @@ public:
virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override;
void emit_input_dialog_callback(String p_text);
+ virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, const FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override;
+ void emit_file_picker_callback(bool p_ok, const Vector<String> &p_selected_paths);
+
virtual TypedArray<Rect2> get_display_cutouts() const override;
virtual Rect2i get_display_safe_area() const override;
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 508c0a30ce..9e430c481e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -59,6 +59,7 @@ import com.google.android.vending.expansion.downloader.*
import org.godotengine.godot.error.Error
import org.godotengine.godot.input.GodotEditText
import org.godotengine.godot.input.GodotInputHandler
+import org.godotengine.godot.io.FilePicker
import org.godotengine.godot.io.directory.DirectoryAccessHandler
import org.godotengine.godot.io.file.FileAccessHandler
import org.godotengine.godot.plugin.AndroidRuntimePlugin
@@ -679,6 +680,9 @@ class Godot(private val context: Context) {
for (plugin in pluginRegistry.allPlugins) {
plugin.onMainActivityResult(requestCode, resultCode, data)
}
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ FilePicker.handleActivityResult(context, requestCode, resultCode, data)
+ }
}
/**
@@ -892,6 +896,13 @@ class Godot(private val context: Context) {
mClipboard.setPrimaryClip(clip)
}
+ @Keep
+ private fun showFilePicker(currentDirectory: String, filename: String, fileMode: Int, filters: Array<String>) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ FilePicker.showFilePicker(context, getActivity(), currentDirectory, filename, fileMode, filters)
+ }
+ }
+
/**
* Popup a dialog to input text.
*/
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 b7adc96196..a06faadc6a 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
@@ -232,6 +232,11 @@ public class GodotLib {
public static native void inputDialogCallback(String p_text);
/**
+ * Invoked on the file picker closed.
+ */
+ public static native void filePickerCallback(boolean p_ok, String[] p_selected_paths);
+
+ /**
* Invoked on the GL thread to configure the height of the virtual keyboard.
*/
public static native void setVirtualKeyboardHeight(int p_height);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/FilePicker.kt b/platform/android/java/lib/src/org/godotengine/godot/io/FilePicker.kt
new file mode 100644
index 0000000000..2befe0583b
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/FilePicker.kt
@@ -0,0 +1,160 @@
+/**************************************************************************/
+/* FilePicker.kt */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+package org.godotengine.godot.io
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.provider.DocumentsContract
+import android.util.Log
+import androidx.annotation.RequiresApi
+import org.godotengine.godot.GodotLib
+import org.godotengine.godot.io.file.MediaStoreData
+
+/**
+ * Utility class for managing file selection and file picker activities.
+ *
+ * It provides methods to launch a file picker and handle the result, supporting various file modes,
+ * including opening files, directories, and saving files.
+ */
+internal class FilePicker {
+ companion object {
+ private const val FILE_PICKER_REQUEST = 1000
+ private val TAG = FilePicker::class.java.simpleName
+
+ // Constants for fileMode values
+ private const val FILE_MODE_OPEN_FILE = 0
+ private const val FILE_MODE_OPEN_FILES = 1
+ private const val FILE_MODE_OPEN_DIR = 2
+ private const val FILE_MODE_OPEN_ANY = 3
+ private const val FILE_MODE_SAVE_FILE = 4
+
+ /**
+ * Handles the result from a file picker activity and processes the selected file(s) or directory.
+ *
+ * @param context The context from which the file picker was launched.
+ * @param requestCode The request code used when starting the file picker activity.
+ * @param resultCode The result code returned by the activity.
+ * @param data The intent data containing the selected file(s) or directory.
+ */
+ @RequiresApi(Build.VERSION_CODES.Q)
+ fun handleActivityResult(context: Context, requestCode: Int, resultCode: Int, data: Intent?) {
+ if (requestCode == FILE_PICKER_REQUEST) {
+ if (resultCode == Activity.RESULT_CANCELED) {
+ Log.d(TAG, "File picker canceled")
+ GodotLib.filePickerCallback(false, emptyArray())
+ return
+ }
+ if (resultCode == Activity.RESULT_OK) {
+ val selectedPaths: MutableList<String> = mutableListOf()
+ // Handle multiple file selection.
+ val clipData = data?.clipData
+ if (clipData != null) {
+ for (i in 0 until clipData.itemCount) {
+ val uri = clipData.getItemAt(i).uri
+ uri?.let {
+ val filepath = MediaStoreData.getFilePathFromUri(context, uri)
+ if (filepath != null) {
+ selectedPaths.add(filepath)
+ } else {
+ Log.d(TAG, "null filepath URI: $it")
+ }
+ }
+ }
+ } else {
+ val uri: Uri? = data?.data
+ uri?.let {
+ val filepath = MediaStoreData.getFilePathFromUri(context, uri)
+ if (filepath != null) {
+ selectedPaths.add(filepath)
+ } else {
+ Log.d(TAG, "null filepath URI: $it")
+ }
+ }
+ }
+
+ if (selectedPaths.isNotEmpty()) {
+ GodotLib.filePickerCallback(true, selectedPaths.toTypedArray())
+ } else {
+ GodotLib.filePickerCallback(false, emptyArray())
+ }
+ }
+ }
+ }
+
+ /**
+ * Launches a file picker activity with specified settings based on the mode, initial directory,
+ * file type filters, and other parameters.
+ *
+ * @param context The context from which to start the file picker.
+ * @param activity The activity instance used to initiate the picker. Required for activity results.
+ * @param currentDirectory The directory path to start the file picker in.
+ * @param filename The name of the file when using save mode.
+ * @param fileMode The mode to operate in, specifying open, save, or directory select.
+ * @param filters Array of MIME types to filter file selection.
+ */
+ @RequiresApi(Build.VERSION_CODES.Q)
+ fun showFilePicker(context: Context, activity: Activity?, currentDirectory: String, filename: String, fileMode: Int, filters: Array<String>) {
+ val intent = when (fileMode) {
+ FILE_MODE_OPEN_DIR -> Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
+ FILE_MODE_SAVE_FILE -> Intent(Intent.ACTION_CREATE_DOCUMENT)
+ else -> Intent(Intent.ACTION_OPEN_DOCUMENT)
+ }
+ val initialDirectory = MediaStoreData.getUriFromDirectoryPath(context, currentDirectory)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && initialDirectory != null) {
+ intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialDirectory)
+ } else {
+ Log.d(TAG, "Error cannot set initial directory")
+ }
+ if (fileMode == FILE_MODE_OPEN_FILES) {
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) // Set multi select for FILE_MODE_OPEN_FILES
+ } else if (fileMode == FILE_MODE_SAVE_FILE) {
+ intent.putExtra(Intent.EXTRA_TITLE, filename) // Set filename for FILE_MODE_SAVE_FILE
+ }
+ // ACTION_OPEN_DOCUMENT_TREE does not support intent type
+ if (fileMode != FILE_MODE_OPEN_DIR) {
+ intent.type = "*/*"
+ if (filters.isNotEmpty()) {
+ if (filters.size == 1) {
+ intent.type = filters[0]
+ } else {
+ intent.putExtra(Intent.EXTRA_MIME_TYPES, filters)
+ }
+ }
+ intent.addCategory(Intent.CATEGORY_OPENABLE)
+ }
+ intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true)
+ activity?.startActivityForResult(intent, FILE_PICKER_REQUEST)
+ }
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt
index 698ea4ca6f..bc96c4d2f1 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/MediaStoreData.kt
@@ -40,6 +40,7 @@ import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
+import android.util.Log
import androidx.annotation.RequiresApi
import java.io.File
@@ -48,6 +49,7 @@ import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.nio.channels.FileChannel
+
/**
* Implementation of [DataAccess] which handles access and interactions with file and data
* under scoped storage via the MediaStore API.
@@ -83,6 +85,10 @@ internal class MediaStoreData(context: Context, filePath: String, accessFlag: Fi
private const val SELECTION_BY_PATH = "${MediaStore.Files.FileColumns.DISPLAY_NAME} = ? " +
" AND ${MediaStore.Files.FileColumns.RELATIVE_PATH} = ?"
+ private const val AUTHORITY_MEDIA_DOCUMENTS = "com.android.providers.media.documents"
+ private const val AUTHORITY_EXTERNAL_STORAGE_DOCUMENTS = "com.android.externalstorage.documents"
+ private const val AUTHORITY_DOWNLOADS_DOCUMENTS = "com.android.providers.downloads.documents"
+
private fun getSelectionByPathArguments(path: String): Array<String> {
return arrayOf(getMediaStoreDisplayName(path), getMediaStoreRelativePath(path))
}
@@ -232,6 +238,72 @@ internal class MediaStoreData(context: Context, filePath: String, accessFlag: Fi
)
return updated > 0
}
+
+ fun getUriFromDirectoryPath(context: Context, directoryPath: String): Uri? {
+ if (!directoryExists(directoryPath)) {
+ return null
+ }
+ // Check if the path is under external storage.
+ val externalStorageRoot = Environment.getExternalStorageDirectory().absolutePath
+ if (directoryPath.startsWith(externalStorageRoot)) {
+ val relativePath = directoryPath.replaceFirst(externalStorageRoot, "").trim('/')
+ val uri = Uri.Builder()
+ .scheme("content")
+ .authority(AUTHORITY_EXTERNAL_STORAGE_DOCUMENTS)
+ .appendPath("document")
+ .appendPath("primary:$relativePath")
+ .build()
+ return uri
+ }
+ return null
+ }
+
+ fun getFilePathFromUri(context: Context, uri: Uri): String? {
+ // Converts content uri to filepath.
+ val id = getIdFromUri(uri) ?: return null
+
+ if (uri.authority == AUTHORITY_EXTERNAL_STORAGE_DOCUMENTS) {
+ val split = id.split(":")
+ val fileName = split.last()
+ val relativePath = split.dropLast(1).joinToString("/")
+ val fullPath = File(Environment.getExternalStorageDirectory(), "$relativePath/$fileName").absolutePath
+ return fullPath
+ } else {
+ val id = id.toLongOrNull() ?: return null
+ val dataItems = queryById(context, id)
+ return if (dataItems.isNotEmpty()) {
+ val dataItem = dataItems[0]
+ File(Environment.getExternalStorageDirectory(), File(dataItem.relativePath, dataItem.displayName).toString()).absolutePath
+ } else {
+ null
+ }
+ }
+ }
+
+ private fun getIdFromUri(uri: Uri): String? {
+ return try {
+ if (uri.authority == AUTHORITY_EXTERNAL_STORAGE_DOCUMENTS || uri.authority == AUTHORITY_MEDIA_DOCUMENTS || uri.authority == AUTHORITY_DOWNLOADS_DOCUMENTS) {
+ val documentId = uri.lastPathSegment ?: throw IllegalArgumentException("Invalid URI: $uri")
+ documentId.substringAfter(":")
+ } else {
+ throw IllegalArgumentException("Unsupported URI format: $uri")
+ }
+ } catch (e: Exception) {
+ Log.d(TAG, "Failed to parse ID from URI: $uri", e)
+ null
+ }
+ }
+
+ private fun directoryExists(path: String): Boolean {
+ return try {
+ val file = File(path)
+ file.isDirectory && file.exists()
+ } catch (e: SecurityException) {
+ Log.d(TAG, "Failed to check directoryExists: $path", e)
+ false
+ }
+ }
+
}
private val id: Long
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 5c51c191ed..eba4daf114 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -550,6 +550,22 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_inputDialogCallback(J
}
}
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_filePickerCallback(JNIEnv *env, jclass clazz, jboolean p_ok, jobjectArray p_selected_paths) {
+ DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton();
+ if (ds) {
+ Vector<String> selected_paths;
+
+ jint length = env->GetArrayLength(p_selected_paths);
+ for (jint i = 0; i < length; ++i) {
+ jstring java_string = (jstring)env->GetObjectArrayElement(p_selected_paths, i);
+ String path = jstring_to_string(java_string, env);
+ selected_paths.push_back(path);
+ env->DeleteLocalRef(java_string);
+ }
+ ds->emit_file_picker_callback(p_ok, selected_paths);
+ }
+}
+
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result) {
String permission = jstring_to_string(p_permission, env);
if (permission == "android.permission.RECORD_AUDIO" && p_result) {
diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h
index de7c628a8d..1e315e410a 100644
--- a/platform/android/java_godot_lib_jni.h
+++ b/platform/android/java_godot_lib_jni.h
@@ -70,6 +70,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHei
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_filePickerCallback(JNIEnv *env, jclass clazz, jboolean p_ok, jobjectArray p_selected_paths);
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 jboolean JNICALL Java_org_godotengine_godot_GodotLib_shouldDispatchInputToRenderThread(JNIEnv *env, jclass clazz);
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index ac4bfdac1f..da11c6a5d3 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -70,6 +70,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_set_clipboard = p_env->GetMethodID(godot_class, "setClipboard", "(Ljava/lang/String;)V");
_has_clipboard = p_env->GetMethodID(godot_class, "hasClipboard", "()Z");
_show_input_dialog = p_env->GetMethodID(godot_class, "showInputDialog", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+ _show_file_picker = p_env->GetMethodID(godot_class, "showFilePicker", "(Ljava/lang/String;Ljava/lang/String;I[Ljava/lang/String;)V");
_request_permission = p_env->GetMethodID(godot_class, "requestPermission", "(Ljava/lang/String;)Z");
_request_permissions = p_env->GetMethodID(godot_class, "requestPermissions", "()Z");
_get_granted_permissions = p_env->GetMethodID(godot_class, "getGrantedPermissions", "()[Ljava/lang/String;");
@@ -288,6 +289,29 @@ Error GodotJavaWrapper::show_input_dialog(const String &p_title, const String &p
}
}
+Error GodotJavaWrapper::show_file_picker(const String &p_current_directory, const String &p_filename, int p_mode, const Vector<String> &p_filters) {
+ if (_show_file_picker) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, ERR_UNCONFIGURED);
+ jstring j_current_directory = env->NewStringUTF(p_current_directory.utf8().get_data());
+ jstring j_filename = env->NewStringUTF(p_filename.utf8().get_data());
+ jint j_mode = p_mode;
+ jobjectArray j_filters = env->NewObjectArray(p_filters.size(), env->FindClass("java/lang/String"), nullptr);
+ for (int i = 0; i < p_filters.size(); ++i) {
+ jstring j_filter = env->NewStringUTF(p_filters[i].utf8().get_data());
+ env->SetObjectArrayElement(j_filters, i, j_filter);
+ env->DeleteLocalRef(j_filter);
+ }
+ env->CallVoidMethod(godot_instance, _show_file_picker, j_current_directory, j_filename, j_mode, j_filters);
+ env->DeleteLocalRef(j_current_directory);
+ env->DeleteLocalRef(j_filename);
+ env->DeleteLocalRef(j_filters);
+ return OK;
+ } else {
+ return ERR_UNCONFIGURED;
+ }
+}
+
bool GodotJavaWrapper::request_permission(const String &p_name) {
if (_request_permission) {
JNIEnv *env = get_jni_env();
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index ed31499984..d344cab836 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -61,6 +61,7 @@ private:
jmethodID _set_clipboard = nullptr;
jmethodID _has_clipboard = nullptr;
jmethodID _show_input_dialog = nullptr;
+ jmethodID _show_file_picker = nullptr;
jmethodID _request_permission = nullptr;
jmethodID _request_permissions = nullptr;
jmethodID _get_granted_permissions = nullptr;
@@ -107,6 +108,7 @@ public:
bool has_has_clipboard();
bool has_clipboard();
Error show_input_dialog(const String &p_title, const String &p_message, const String &p_existing_text);
+ Error show_file_picker(const String &p_current_directory, const String &p_filename, int p_mode, const Vector<String> &p_filters);
bool request_permission(const String &p_name);
bool request_permissions();
Vector<String> get_granted_permissions() const;
diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm
index de1eb939e3..af6f07cda0 100644
--- a/platform/ios/display_server_ios.mm
+++ b/platform/ios/display_server_ios.mm
@@ -372,6 +372,7 @@ bool DisplayServerIOS::has_feature(Feature p_feature) const {
// case FEATURE_NATIVE_DIALOG:
// case FEATURE_NATIVE_DIALOG_INPUT:
// case FEATURE_NATIVE_DIALOG_FILE:
+ // case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
// case FEATURE_NATIVE_ICON:
// case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_CLIPBOARD:
diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp
index 4b5ab09fec..043acc7a3a 100644
--- a/platform/linuxbsd/wayland/display_server_wayland.cpp
+++ b/platform/linuxbsd/wayland/display_server_wayland.cpp
@@ -218,7 +218,8 @@ bool DisplayServerWayland::has_feature(Feature p_feature) const {
//case FEATURE_NATIVE_DIALOG:
//case FEATURE_NATIVE_DIALOG_INPUT:
#ifdef DBUS_ENABLED
- case FEATURE_NATIVE_DIALOG_FILE: {
+ case FEATURE_NATIVE_DIALOG_FILE:
+ case FEATURE_NATIVE_DIALOG_FILE_EXTRA: {
return true;
} break;
#endif
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index ccc9e994a7..ae7231bd09 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -131,6 +131,7 @@ bool DisplayServerX11::has_feature(Feature p_feature) const {
case FEATURE_ICON:
#ifdef DBUS_ENABLED
case FEATURE_NATIVE_DIALOG_FILE:
+ case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
#endif
//case FEATURE_NATIVE_DIALOG:
//case FEATURE_NATIVE_DIALOG_INPUT:
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index d2b74d6b8f..6ea4c763d7 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -754,6 +754,7 @@ bool DisplayServerMacOS::has_feature(Feature p_feature) const {
case FEATURE_NATIVE_DIALOG:
case FEATURE_NATIVE_DIALOG_INPUT:
case FEATURE_NATIVE_DIALOG_FILE:
+ case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
case FEATURE_IME:
case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_HIDPI:
diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp
index 0161d95d88..0b013662d1 100644
--- a/platform/web/display_server_web.cpp
+++ b/platform/web/display_server_web.cpp
@@ -1135,6 +1135,7 @@ bool DisplayServerWeb::has_feature(Feature p_feature) const {
//case FEATURE_NATIVE_DIALOG:
//case FEATURE_NATIVE_DIALOG_INPUT:
//case FEATURE_NATIVE_DIALOG_FILE:
+ //case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
//case FEATURE_NATIVE_ICON:
//case FEATURE_WINDOW_TRANSPARENCY:
//case FEATURE_KEEP_SCREEN_ON:
diff --git a/platform/web/emscripten_helpers.py b/platform/web/emscripten_helpers.py
index 8fcabb21c7..3122271a71 100644
--- a/platform/web/emscripten_helpers.py
+++ b/platform/web/emscripten_helpers.py
@@ -3,6 +3,8 @@ import os
from SCons.Util import WhereIs
+from platform_methods import get_build_version
+
def run_closure_compiler(target, source, env, for_signature):
closure_bin = os.path.join(
@@ -21,22 +23,6 @@ def run_closure_compiler(target, source, env, for_signature):
return " ".join(cmd)
-def get_build_version():
- import version
-
- name = "custom_build"
- if os.getenv("BUILD_NAME") is not None:
- name = os.getenv("BUILD_NAME")
- v = "%d.%d" % (version.major, version.minor)
- if version.patch > 0:
- v += ".%d" % version.patch
- status = version.status
- if os.getenv("GODOT_VERSION_STATUS") is not None:
- status = str(os.getenv("GODOT_VERSION_STATUS"))
- v += ".%s.%s" % (status, name)
- return v
-
-
def create_engine_file(env, target, source, externs, threads_enabled):
if env["use_closure_compiler"]:
return env.BuildJS(target, source, JSEXTERNS=externs)
@@ -84,7 +70,7 @@ def create_template_zip(env, js, wasm, worker, side):
cache.append("godot.editor.worker.js")
opt_cache = ["godot.editor.wasm"]
subst_dict = {
- "___GODOT_VERSION___": get_build_version(),
+ "___GODOT_VERSION___": get_build_version(False),
"___GODOT_NAME___": "GodotEngine",
"___GODOT_CACHE___": json.dumps(cache),
"___GODOT_OPT_CACHE___": json.dumps(opt_cache),
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index c8e8e21347..36d6f24408 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -131,6 +131,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const {
case FEATURE_NATIVE_DIALOG:
case FEATURE_NATIVE_DIALOG_INPUT:
case FEATURE_NATIVE_DIALOG_FILE:
+ case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
case FEATURE_SWAP_BUFFERS:
case FEATURE_KEEP_SCREEN_ON:
case FEATURE_TEXT_TO_SPEECH:
diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp
index 55856403bf..f785ef0ee9 100644
--- a/scene/2d/camera_2d.cpp
+++ b/scene/2d/camera_2d.cpp
@@ -304,6 +304,7 @@ void Camera2D::_notification(int p_what) {
_interpolation_data.xform_prev = _interpolation_data.xform_curr;
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (is_physics_interpolated_and_enabled()) {
_update_scroll();
diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp
index 9857401cb1..dfce5d43bd 100644
--- a/scene/2d/gpu_particles_2d.cpp
+++ b/scene/2d/gpu_particles_2d.cpp
@@ -698,6 +698,8 @@ void GPUParticles2D::_notification(int p_what) {
RS::get_singleton()->particles_set_subemitter(particles, RID());
} break;
+ case NOTIFICATION_SUSPENDED:
+ case NOTIFICATION_UNSUSPENDED:
case NOTIFICATION_PAUSED:
case NOTIFICATION_UNPAUSED: {
if (is_inside_tree()) {
diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp
index a80ad582d7..77f181b28a 100644
--- a/scene/2d/light_occluder_2d.cpp
+++ b/scene/2d/light_occluder_2d.cpp
@@ -92,6 +92,7 @@ void OccluderPolygon2D::set_polygon(const Vector<Vector2> &p_polygon) {
rect_cache_dirty = true;
RS::get_singleton()->canvas_occluder_polygon_set_shape(occ_polygon, p_polygon, closed);
emit_changed();
+ update_configuration_warning();
}
Vector<Vector2> OccluderPolygon2D::get_polygon() const {
diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp
index 1bbb00c9b7..7972324e1b 100644
--- a/scene/2d/navigation_agent_2d.cpp
+++ b/scene/2d/navigation_agent_2d.cpp
@@ -255,12 +255,20 @@ void NavigationAgent2D::_notification(int p_what) {
#endif // DEBUG_ENABLED
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (agent_parent) {
NavigationServer2D::get_singleton()->agent_set_paused(get_rid(), !agent_parent->can_process());
}
} break;
+ case NOTIFICATION_UNSUSPENDED: {
+ if (get_tree()->is_paused()) {
+ break;
+ }
+ [[fallthrough]];
+ }
+
case NOTIFICATION_UNPAUSED: {
if (agent_parent) {
NavigationServer2D::get_singleton()->agent_set_paused(get_rid(), !agent_parent->can_process());
diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp
index b872d73413..7d52041e6a 100644
--- a/scene/2d/navigation_obstacle_2d.cpp
+++ b/scene/2d/navigation_obstacle_2d.cpp
@@ -106,6 +106,7 @@ void NavigationObstacle2D::_notification(int p_what) {
#endif // DEBUG_ENABLED
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (!can_process()) {
map_before_pause = map_current;
@@ -117,6 +118,13 @@ void NavigationObstacle2D::_notification(int p_what) {
NavigationServer2D::get_singleton()->obstacle_set_paused(obstacle, !can_process());
} break;
+ case NOTIFICATION_UNSUSPENDED: {
+ if (get_tree()->is_paused()) {
+ break;
+ }
+ [[fallthrough]];
+ }
+
case NOTIFICATION_UNPAUSED: {
if (!can_process()) {
map_before_pause = map_current;
diff --git a/scene/2d/touch_screen_button.cpp b/scene/2d/touch_screen_button.cpp
index af7f94387b..f003442265 100644
--- a/scene/2d/touch_screen_button.cpp
+++ b/scene/2d/touch_screen_button.cpp
@@ -187,6 +187,7 @@ void TouchScreenButton::_notification(int p_what) {
}
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (is_pressed()) {
_release();
diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp
index d67d4e42a0..77665317a8 100644
--- a/scene/3d/camera_3d.cpp
+++ b/scene/3d/camera_3d.cpp
@@ -236,6 +236,7 @@ void Camera3D::_notification(int p_what) {
}
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (is_physics_interpolated_and_enabled() && is_inside_tree() && is_visible_in_tree()) {
_physics_interpolation_ensure_transform_calculated(true);
diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp
index 1626fc1e96..83609dfa91 100644
--- a/scene/3d/gpu_particles_3d.cpp
+++ b/scene/3d/gpu_particles_3d.cpp
@@ -513,6 +513,8 @@ void GPUParticles3D::_notification(int p_what) {
RS::get_singleton()->particles_set_subemitter(particles, RID());
} break;
+ case NOTIFICATION_SUSPENDED:
+ case NOTIFICATION_UNSUSPENDED:
case NOTIFICATION_PAUSED:
case NOTIFICATION_UNPAUSED: {
if (is_inside_tree()) {
diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp
index 239fe279db..bd88c0e327 100644
--- a/scene/3d/navigation_agent_3d.cpp
+++ b/scene/3d/navigation_agent_3d.cpp
@@ -274,12 +274,20 @@ void NavigationAgent3D::_notification(int p_what) {
#endif // DEBUG_ENABLED
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (agent_parent) {
NavigationServer3D::get_singleton()->agent_set_paused(get_rid(), !agent_parent->can_process());
}
} break;
+ case NOTIFICATION_UNSUSPENDED: {
+ if (get_tree()->is_paused()) {
+ break;
+ }
+ [[fallthrough]];
+ }
+
case NOTIFICATION_UNPAUSED: {
if (agent_parent) {
NavigationServer3D::get_singleton()->agent_set_paused(get_rid(), !agent_parent->can_process());
diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp
index 85630b8c6b..0ea6533094 100644
--- a/scene/3d/navigation_obstacle_3d.cpp
+++ b/scene/3d/navigation_obstacle_3d.cpp
@@ -121,6 +121,7 @@ void NavigationObstacle3D::_notification(int p_what) {
#endif // DEBUG_ENABLED
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (!can_process()) {
map_before_pause = map_current;
@@ -132,6 +133,13 @@ void NavigationObstacle3D::_notification(int p_what) {
NavigationServer3D::get_singleton()->obstacle_set_paused(obstacle, !can_process());
} break;
+ case NOTIFICATION_UNSUSPENDED: {
+ if (get_tree()->is_paused()) {
+ break;
+ }
+ [[fallthrough]];
+ }
+
case NOTIFICATION_UNPAUSED: {
if (!can_process()) {
map_before_pause = map_current;
diff --git a/scene/audio/audio_stream_player_internal.cpp b/scene/audio/audio_stream_player_internal.cpp
index 1007dd3df5..0b507847dd 100644
--- a/scene/audio/audio_stream_player_internal.cpp
+++ b/scene/audio/audio_stream_player_internal.cpp
@@ -114,6 +114,7 @@ void AudioStreamPlayerInternal::notification(int p_what) {
stream_playbacks.clear();
} break;
+ case Node::NOTIFICATION_SUSPENDED:
case Node::NOTIFICATION_PAUSED: {
if (!node->can_process()) {
// Node can't process so we start fading out to silence
@@ -121,6 +122,13 @@ void AudioStreamPlayerInternal::notification(int p_what) {
}
} break;
+ case Node::NOTIFICATION_UNSUSPENDED: {
+ if (node->get_tree()->is_paused()) {
+ break;
+ }
+ [[fallthrough]];
+ }
+
case Node::NOTIFICATION_UNPAUSED: {
set_stream_paused(false);
} break;
diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp
index 8f0dc9828b..1a1780d737 100644
--- a/scene/debugger/scene_debugger.cpp
+++ b/scene/debugger/scene_debugger.cpp
@@ -33,20 +33,34 @@
#include "scene_debugger.h"
#include "core/debugger/engine_debugger.h"
-#include "core/debugger/engine_profiler.h"
#include "core/io/marshalls.h"
#include "core/object/script_language.h"
#include "core/templates/local_vector.h"
+#include "scene/2d/physics/collision_object_2d.h"
+#include "scene/2d/physics/collision_polygon_2d.h"
+#include "scene/2d/physics/collision_shape_2d.h"
+#ifndef _3D_DISABLED
+#include "scene/3d/label_3d.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/3d/physics/collision_object_3d.h"
+#include "scene/3d/physics/collision_shape_3d.h"
+#include "scene/3d/sprite_3d.h"
+#include "scene/resources/surface_tool.h"
+#endif // _3D_DISABLED
+#include "scene/gui/popup_menu.h"
+#include "scene/main/canvas_layer.h"
#include "scene/main/scene_tree.h"
#include "scene/main/window.h"
#include "scene/resources/packed_scene.h"
-
-SceneDebugger *SceneDebugger::singleton = nullptr;
+#include "scene/theme/theme_db.h"
SceneDebugger::SceneDebugger() {
singleton = this;
+
#ifdef DEBUG_ENABLED
LiveEditor::singleton = memnew(LiveEditor);
+ RuntimeNodeSelect::singleton = memnew(RuntimeNodeSelect);
+
EngineDebugger::register_message_capture("scene", EngineDebugger::Capture(nullptr, SceneDebugger::parse_message));
#endif
}
@@ -58,7 +72,13 @@ SceneDebugger::~SceneDebugger() {
memdelete(LiveEditor::singleton);
LiveEditor::singleton = nullptr;
}
+
+ if (RuntimeNodeSelect::singleton) {
+ memdelete(RuntimeNodeSelect::singleton);
+ RuntimeNodeSelect::singleton = nullptr;
+ }
#endif
+
singleton = nullptr;
}
@@ -80,10 +100,15 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra
if (!scene_tree) {
return ERR_UNCONFIGURED;
}
+
LiveEditor *live_editor = LiveEditor::get_singleton();
if (!live_editor) {
return ERR_UNCONFIGURED;
}
+ RuntimeNodeSelect *runtime_node_select = RuntimeNodeSelect::get_singleton();
+ if (!runtime_node_select) {
+ return ERR_UNCONFIGURED;
+ }
r_captured = true;
if (p_msg == "request_scene_tree") { // Scene tree
@@ -101,22 +126,34 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra
ObjectID id = p_args[0];
_send_object_id(id);
- } else if (p_msg == "override_camera_2D:set") { // Camera
+ } else if (p_msg == "suspend_changed") {
ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
- bool enforce = p_args[0];
- scene_tree->get_root()->enable_canvas_transform_override(enforce);
+ bool suspended = p_args[0];
+ scene_tree->set_suspend(suspended);
+ runtime_node_select->_update_input_state();
- } else if (p_msg == "override_camera_2D:transform") {
- ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
- Transform2D transform = p_args[0];
- scene_tree->get_root()->set_canvas_transform_override(transform);
-#ifndef _3D_DISABLED
- } else if (p_msg == "override_camera_3D:set") {
+ } else if (p_msg == "next_frame") {
+ _next_frame();
+
+ } else if (p_msg == "override_cameras") { // Camera
ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
bool enable = p_args[0];
+ bool from_editor = p_args[1];
+ scene_tree->get_root()->enable_canvas_transform_override(enable);
+#ifndef _3D_DISABLED
scene_tree->get_root()->enable_camera_3d_override(enable);
+#endif // _3D_DISABLED
+ runtime_node_select->_set_camera_override_enabled(enable && !from_editor);
- } else if (p_msg == "override_camera_3D:transform") {
+ } else if (p_msg == "transform_camera_2d") {
+ ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
+ Transform2D transform = p_args[0];
+ scene_tree->get_root()->set_canvas_transform_override(transform);
+
+ runtime_node_select->_queue_selection_update();
+
+#ifndef _3D_DISABLED
+ } else if (p_msg == "transform_camera_3d") {
ERR_FAIL_COND_V(p_args.size() < 5, ERR_INVALID_DATA);
Transform3D transform = p_args[0];
bool is_perspective = p_args[1];
@@ -129,95 +166,142 @@ Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Arra
scene_tree->get_root()->set_camera_3d_override_orthogonal(size_or_fov, depth_near, depth_far);
}
scene_tree->get_root()->set_camera_3d_override_transform(transform);
+
+ runtime_node_select->_queue_selection_update();
#endif // _3D_DISABLED
+
} else if (p_msg == "set_object_property") {
ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
_set_object_property(p_args[0], p_args[1], p_args[2]);
- } else if (!p_msg.begins_with("live_")) { // Live edits below.
- return ERR_SKIP;
- } else if (p_msg == "live_set_root") {
- ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
- live_editor->_root_func(p_args[0], p_args[1]);
+ runtime_node_select->_queue_selection_update();
+
+ } else if (p_msg.begins_with("live_")) { /// Live Edit
+ if (p_msg == "live_set_root") {
+ ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
+ live_editor->_root_func(p_args[0], p_args[1]);
+
+ } else if (p_msg == "live_node_path") {
+ ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
+ live_editor->_node_path_func(p_args[0], p_args[1]);
+
+ } else if (p_msg == "live_res_path") {
+ ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
+ live_editor->_res_path_func(p_args[0], p_args[1]);
+
+ } else if (p_msg == "live_node_prop_res") {
+ ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
+ live_editor->_node_set_res_func(p_args[0], p_args[1], p_args[2]);
+
+ } else if (p_msg == "live_node_prop") {
+ ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
+ live_editor->_node_set_func(p_args[0], p_args[1], p_args[2]);
+
+ } else if (p_msg == "live_res_prop_res") {
+ ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
+ live_editor->_res_set_res_func(p_args[0], p_args[1], p_args[2]);
+
+ } else if (p_msg == "live_res_prop") {
+ ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
+ live_editor->_res_set_func(p_args[0], p_args[1], p_args[2]);
+
+ } else if (p_msg == "live_node_call") {
+ ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
+ LocalVector<Variant> args;
+ LocalVector<Variant *> argptrs;
+ args.resize(p_args.size() - 2);
+ argptrs.resize(args.size());
+ for (uint32_t i = 0; i < args.size(); i++) {
+ args[i] = p_args[i + 2];
+ argptrs[i] = &args[i];
+ }
+ live_editor->_node_call_func(p_args[0], p_args[1], argptrs.size() ? (const Variant **)argptrs.ptr() : nullptr, argptrs.size());
+
+ } else if (p_msg == "live_res_call") {
+ ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
+ LocalVector<Variant> args;
+ LocalVector<Variant *> argptrs;
+ args.resize(p_args.size() - 2);
+ argptrs.resize(args.size());
+ for (uint32_t i = 0; i < args.size(); i++) {
+ args[i] = p_args[i + 2];
+ argptrs[i] = &args[i];
+ }
+ live_editor->_res_call_func(p_args[0], p_args[1], argptrs.size() ? (const Variant **)argptrs.ptr() : nullptr, argptrs.size());
- } else if (p_msg == "live_node_path") {
- ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
- live_editor->_node_path_func(p_args[0], p_args[1]);
+ } else if (p_msg == "live_create_node") {
+ ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
+ live_editor->_create_node_func(p_args[0], p_args[1], p_args[2]);
- } else if (p_msg == "live_res_path") {
- ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
- live_editor->_res_path_func(p_args[0], p_args[1]);
+ } else if (p_msg == "live_instantiate_node") {
+ ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
+ live_editor->_instance_node_func(p_args[0], p_args[1], p_args[2]);
- } else if (p_msg == "live_node_prop_res") {
- ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
- live_editor->_node_set_res_func(p_args[0], p_args[1], p_args[2]);
+ } else if (p_msg == "live_remove_node") {
+ ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
+ live_editor->_remove_node_func(p_args[0]);
- } else if (p_msg == "live_node_prop") {
- ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
- live_editor->_node_set_func(p_args[0], p_args[1], p_args[2]);
+ if (!runtime_node_select->has_selection) {
+ runtime_node_select->_clear_selection();
+ }
- } else if (p_msg == "live_res_prop_res") {
- ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
- live_editor->_res_set_res_func(p_args[0], p_args[1], p_args[2]);
+ } else if (p_msg == "live_remove_and_keep_node") {
+ ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
+ live_editor->_remove_and_keep_node_func(p_args[0], p_args[1]);
- } else if (p_msg == "live_res_prop") {
- ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
- live_editor->_res_set_func(p_args[0], p_args[1], p_args[2]);
+ if (!runtime_node_select->has_selection) {
+ runtime_node_select->_clear_selection();
+ }
- } else if (p_msg == "live_node_call") {
- ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
- LocalVector<Variant> args;
- LocalVector<Variant *> argptrs;
- args.resize(p_args.size() - 2);
- argptrs.resize(args.size());
- for (uint32_t i = 0; i < args.size(); i++) {
- args[i] = p_args[i + 2];
- argptrs[i] = &args[i];
- }
- live_editor->_node_call_func(p_args[0], p_args[1], argptrs.size() ? (const Variant **)argptrs.ptr() : nullptr, argptrs.size());
+ } else if (p_msg == "live_restore_node") {
+ ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
+ live_editor->_restore_node_func(p_args[0], p_args[1], p_args[2]);
- } else if (p_msg == "live_res_call") {
- ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
- LocalVector<Variant> args;
- LocalVector<Variant *> argptrs;
- args.resize(p_args.size() - 2);
- argptrs.resize(args.size());
- for (uint32_t i = 0; i < args.size(); i++) {
- args[i] = p_args[i + 2];
- argptrs[i] = &args[i];
+ } else if (p_msg == "live_duplicate_node") {
+ ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
+ live_editor->_duplicate_node_func(p_args[0], p_args[1]);
+
+ } else if (p_msg == "live_reparent_node") {
+ ERR_FAIL_COND_V(p_args.size() < 4, ERR_INVALID_DATA);
+ live_editor->_reparent_node_func(p_args[0], p_args[1], p_args[2], p_args[3]);
+
+ } else {
+ return ERR_SKIP;
}
- live_editor->_res_call_func(p_args[0], p_args[1], argptrs.size() ? (const Variant **)argptrs.ptr() : nullptr, argptrs.size());
- } else if (p_msg == "live_create_node") {
- ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
- live_editor->_create_node_func(p_args[0], p_args[1], p_args[2]);
+ } else if (p_msg.begins_with("runtime_node_select_")) { /// Runtime Node Selection
+ if (p_msg == "runtime_node_select_setup") {
+ runtime_node_select->_setup();
- } else if (p_msg == "live_instantiate_node") {
- ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
- live_editor->_instance_node_func(p_args[0], p_args[1], p_args[2]);
+ } else if (p_msg == "runtime_node_select_set_type") {
+ ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
+ RuntimeNodeSelect::NodeType type = (RuntimeNodeSelect::NodeType)(int)p_args[0];
+ runtime_node_select->_node_set_type(type);
- } else if (p_msg == "live_remove_node") {
- ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
- live_editor->_remove_node_func(p_args[0]);
+ } else if (p_msg == "runtime_node_select_set_mode") {
+ ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
+ RuntimeNodeSelect::SelectMode mode = (RuntimeNodeSelect::SelectMode)(int)p_args[0];
+ runtime_node_select->_select_set_mode(mode);
- } else if (p_msg == "live_remove_and_keep_node") {
- ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
- live_editor->_remove_and_keep_node_func(p_args[0], p_args[1]);
+ } else if (p_msg == "runtime_node_select_set_visible") {
+ ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA);
+ bool visible = p_args[0];
+ runtime_node_select->_set_selection_visible(visible);
- } else if (p_msg == "live_restore_node") {
- ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA);
- live_editor->_restore_node_func(p_args[0], p_args[1], p_args[2]);
+ } else if (p_msg == "runtime_node_select_reset_camera_2d") {
+ runtime_node_select->_reset_camera_2d();
- } else if (p_msg == "live_duplicate_node") {
- ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA);
- live_editor->_duplicate_node_func(p_args[0], p_args[1]);
+ } else if (p_msg == "runtime_node_select_reset_camera_3d") {
+ runtime_node_select->_reset_camera_3d();
+
+ } else {
+ return ERR_SKIP;
+ }
- } else if (p_msg == "live_reparent_node") {
- ERR_FAIL_COND_V(p_args.size() < 4, ERR_INVALID_DATA);
- live_editor->_reparent_node_func(p_args[0], p_args[1], p_args[2], p_args[3]);
} else {
r_captured = false;
}
+
return OK;
}
@@ -262,6 +346,9 @@ void SceneDebugger::_send_object_id(ObjectID p_id, int p_max_size) {
return;
}
+ Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id));
+ RuntimeNodeSelect::get_singleton()->_select_node(node);
+
Array arr;
obj.serialize(arr);
EngineDebugger::get_singleton()->send_message("scene:inspect_object", arr);
@@ -282,6 +369,16 @@ void SceneDebugger::_set_object_property(ObjectID p_id, const String &p_property
obj->set(prop_name, p_value);
}
+void SceneDebugger::_next_frame() {
+ SceneTree *scene_tree = SceneTree::get_singleton();
+ if (!scene_tree->is_suspended()) {
+ return;
+ }
+
+ scene_tree->set_suspend(false);
+ RenderingServer::get_singleton()->connect("frame_post_draw", callable_mp(scene_tree, &SceneTree::set_suspend).bind(true), Object::CONNECT_ONE_SHOT);
+}
+
void SceneDebugger::add_to_cache(const String &p_filename, Node *p_node) {
LiveEditor *debugger = LiveEditor::get_singleton();
if (!debugger) {
@@ -582,7 +679,6 @@ void SceneDebuggerTree::deserialize(const Array &p_arr) {
}
/// LiveEditor
-LiveEditor *LiveEditor::singleton = nullptr;
LiveEditor *LiveEditor::get_singleton() {
return singleton;
}
@@ -1091,4 +1187,942 @@ void LiveEditor::_reparent_node_func(const NodePath &p_at, const NodePath &p_new
}
}
+/// RuntimeNodeSelect
+RuntimeNodeSelect *RuntimeNodeSelect::get_singleton() {
+ return singleton;
+}
+
+RuntimeNodeSelect::~RuntimeNodeSelect() {
+ if (selection_list && !selection_list->is_visible()) {
+ memdelete(selection_list);
+ }
+
+ if (sbox_2d_canvas.is_valid()) {
+ RS::get_singleton()->free(sbox_2d_canvas);
+ RS::get_singleton()->free(sbox_2d_ci);
+ }
+
+#ifndef _3D_DISABLED
+ if (sbox_3d_instance.is_valid()) {
+ RS::get_singleton()->free(sbox_3d_instance);
+ RS::get_singleton()->free(sbox_3d_instance_ofs);
+ RS::get_singleton()->free(sbox_3d_instance_xray);
+ RS::get_singleton()->free(sbox_3d_instance_xray_ofs);
+ }
+#endif // _3D_DISABLED
+}
+
+void RuntimeNodeSelect::_setup() {
+ Window *root = SceneTree::get_singleton()->get_root();
+ ERR_FAIL_COND(root->is_connected(SceneStringName(window_input), callable_mp(this, &RuntimeNodeSelect::_root_window_input)));
+
+ root->connect(SceneStringName(window_input), callable_mp(this, &RuntimeNodeSelect::_root_window_input));
+ root->connect("size_changed", callable_mp(this, &RuntimeNodeSelect::_queue_selection_update), CONNECT_DEFERRED);
+
+ selection_list = memnew(PopupMenu);
+ selection_list->set_theme(ThemeDB::get_singleton()->get_default_theme());
+ selection_list->set_auto_translate_mode(Node::AUTO_TRANSLATE_MODE_DISABLED);
+ selection_list->set_force_native(true);
+ selection_list->connect("index_pressed", callable_mp(this, &RuntimeNodeSelect::_items_popup_index_pressed).bind(selection_list));
+ selection_list->connect("popup_hide", callable_mp(Object::cast_to<Node>(root), &Node::remove_child).bind(selection_list));
+
+ panner.instantiate();
+ panner->set_callbacks(callable_mp(this, &RuntimeNodeSelect::_pan_callback), callable_mp(this, &RuntimeNodeSelect::_zoom_callback));
+
+ /// 2D Selection Box Generation
+
+ sbox_2d_canvas = RS::get_singleton()->canvas_create();
+ sbox_2d_ci = RS::get_singleton()->canvas_item_create();
+ RS::get_singleton()->viewport_attach_canvas(root->get_viewport_rid(), sbox_2d_canvas);
+ RS::get_singleton()->canvas_item_set_parent(sbox_2d_ci, sbox_2d_canvas);
+
+#ifndef _3D_DISABLED
+ cursor = Cursor();
+
+ /// 3D Selection Box Generation
+ // Copied from the Node3DEditor implementation.
+
+ // Use two AABBs to create the illusion of a slightly thicker line.
+ AABB aabb(Vector3(), Vector3(1, 1, 1));
+
+ // Create a x-ray (visible through solid surfaces) and standard version of the selection box.
+ // Both will be drawn at the same position, but with different opacity.
+ // This lets the user see where the selection is while still having a sense of depth.
+ Ref<SurfaceTool> st = memnew(SurfaceTool);
+ Ref<SurfaceTool> st_xray = memnew(SurfaceTool);
+
+ st->begin(Mesh::PRIMITIVE_LINES);
+ st_xray->begin(Mesh::PRIMITIVE_LINES);
+ for (int i = 0; i < 12; i++) {
+ Vector3 a, b;
+ aabb.get_edge(i, a, b);
+
+ st->add_vertex(a);
+ st->add_vertex(b);
+ st_xray->add_vertex(a);
+ st_xray->add_vertex(b);
+ }
+
+ Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
+ mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
+ // In the original Node3DEditor, this value would be fetched from the "editors/3d/selection_box_color" editor property,
+ // but since this is not accessible from here, we will just use the default value.
+ const Color selection_color_3d = Color(1, 0.5, 0);
+ mat->set_albedo(selection_color_3d);
+ mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+ st->set_material(mat);
+ sbox_3d_mesh = st->commit();
+
+ Ref<StandardMaterial3D> mat_xray = memnew(StandardMaterial3D);
+ mat_xray->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
+ mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);
+ mat_xray->set_albedo(selection_color_3d * Color(1, 1, 1, 0.15));
+ mat_xray->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+ st_xray->set_material(mat_xray);
+ sbox_3d_mesh_xray = st_xray->commit();
+#endif // _3D_DISABLED
+
+ SceneTree::get_singleton()->connect("process_frame", callable_mp(this, &RuntimeNodeSelect::_process_frame));
+ SceneTree::get_singleton()->connect("physics_frame", callable_mp(this, &RuntimeNodeSelect::_physics_frame));
+
+ // This function will be called before the root enters the tree at first when the Game view is passing its settings to
+ // the debugger, so queue the update for after it enters.
+ root->connect(SceneStringName(tree_entered), callable_mp(this, &RuntimeNodeSelect::_update_input_state), Object::CONNECT_ONE_SHOT);
+}
+
+void RuntimeNodeSelect::_node_set_type(NodeType p_type) {
+ node_select_type = p_type;
+ _update_input_state();
+}
+
+void RuntimeNodeSelect::_select_set_mode(SelectMode p_mode) {
+ node_select_mode = p_mode;
+}
+
+void RuntimeNodeSelect::_set_camera_override_enabled(bool p_enabled) {
+ camera_override = p_enabled;
+
+ if (p_enabled) {
+ _update_view_2d();
+ }
+
+#ifndef _3D_DISABLED
+ if (camera_first_override) {
+ _reset_camera_2d();
+ _reset_camera_3d();
+
+ camera_first_override = false;
+ } else if (p_enabled) {
+ _update_view_2d();
+
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform());
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_perspective(CAMERA_BASE_FOV * cursor.fov_scale, CAMERA_ZNEAR, CAMERA_ZFAR);
+ }
+#endif // _3D_DISABLED
+}
+
+void RuntimeNodeSelect::_root_window_input(const Ref<InputEvent> &p_event) {
+ Window *root = SceneTree::get_singleton()->get_root();
+ if (node_select_type == NODE_TYPE_NONE || selection_list->is_visible()) {
+ // Workaround for platforms that don't allow subwindows.
+ if (selection_list->is_visible() && selection_list->is_embedded()) {
+ root->set_disable_input_override(false);
+ selection_list->push_input(p_event);
+ callable_mp(root->get_viewport(), &Viewport::set_disable_input_override).call_deferred(true);
+ }
+
+ return;
+ }
+
+ if (camera_override) {
+ if (node_select_type == NODE_TYPE_2D) {
+ if (panner->gui_input(p_event, Rect2(Vector2(), root->get_size()))) {
+ return;
+ }
+ } else if (node_select_type == NODE_TYPE_3D) {
+#ifndef _3D_DISABLED
+ if (root->get_camera_3d() && _handle_3d_input(p_event)) {
+ return;
+ }
+#endif // _3D_DISABLED
+ }
+ }
+
+ Ref<InputEventMouseButton> b = p_event;
+ if (!b.is_valid() || !b->is_pressed()) {
+ return;
+ }
+
+ list_shortcut_pressed = node_select_mode == SELECT_MODE_SINGLE && b->get_button_index() == MouseButton::RIGHT && b->is_alt_pressed();
+ if (list_shortcut_pressed || b->get_button_index() == MouseButton::LEFT) {
+ selection_position = b->get_position();
+ }
+}
+
+void RuntimeNodeSelect::_items_popup_index_pressed(int p_index, PopupMenu *p_popup) {
+ Object *obj = p_popup->get_item_metadata(p_index).get_validated_object();
+ if (!obj) {
+ return;
+ }
+
+ Array message;
+ message.append(obj->get_instance_id());
+ EngineDebugger::get_singleton()->send_message("remote_node_clicked", message);
+}
+
+void RuntimeNodeSelect::_update_input_state() {
+ SceneTree *scene_tree = SceneTree::get_singleton();
+ // This function can be called at the very beginning, when the root hasn't entered the tree yet.
+ // So check first to avoid a crash.
+ if (!scene_tree->get_root()->is_inside_tree()) {
+ return;
+ }
+
+ bool disable_input = scene_tree->is_suspended() || node_select_type != RuntimeNodeSelect::NODE_TYPE_NONE;
+ Input::get_singleton()->set_disable_input(disable_input);
+ Input::get_singleton()->set_mouse_mode_override_enabled(disable_input);
+ scene_tree->get_root()->set_disable_input_override(disable_input);
+}
+
+void RuntimeNodeSelect::_process_frame() {
+#ifndef _3D_DISABLED
+ if (camera_freelook) {
+ Transform3D transform = _get_cursor_transform();
+ Vector3 forward = transform.basis.xform(Vector3(0, 0, -1));
+ const Vector3 right = transform.basis.xform(Vector3(1, 0, 0));
+ Vector3 up = transform.basis.xform(Vector3(0, 1, 0));
+
+ Vector3 direction;
+
+ Input *input = Input::get_singleton();
+ bool was_input_disabled = input->is_input_disabled();
+ if (was_input_disabled) {
+ input->set_disable_input(false);
+ }
+
+ if (input->is_physical_key_pressed(Key::A)) {
+ direction -= right;
+ }
+ if (input->is_physical_key_pressed(Key::D)) {
+ direction += right;
+ }
+ if (input->is_physical_key_pressed(Key::W)) {
+ direction += forward;
+ }
+ if (input->is_physical_key_pressed(Key::S)) {
+ direction -= forward;
+ }
+ if (input->is_physical_key_pressed(Key::E)) {
+ direction += up;
+ }
+ if (input->is_physical_key_pressed(Key::Q)) {
+ direction -= up;
+ }
+
+ real_t speed = FREELOOK_BASE_SPEED;
+ if (input->is_physical_key_pressed(Key::SHIFT)) {
+ speed *= 3.0;
+ }
+ if (input->is_physical_key_pressed(Key::ALT)) {
+ speed *= 0.333333;
+ }
+
+ if (was_input_disabled) {
+ input->set_disable_input(true);
+ }
+
+ if (direction != Vector3()) {
+ // Calculate the process time manually, as the time scale is frozen.
+ const double process_time = (1.0 / Engine::get_singleton()->get_frames_per_second()) * Engine::get_singleton()->get_unfrozen_time_scale();
+ const Vector3 motion = direction * speed * process_time;
+ cursor.pos += motion;
+ cursor.eye_pos += motion;
+
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform());
+ }
+ }
+#endif // _3D_DISABLED
+
+ if (selection_update_queued || !SceneTree::get_singleton()->is_suspended()) {
+ selection_update_queued = false;
+ if (has_selection) {
+ _update_selection();
+ }
+ }
+}
+
+void RuntimeNodeSelect::_physics_frame() {
+ if (!Math::is_inf(selection_position.x) || !Math::is_inf(selection_position.y)) {
+ _click_point();
+ selection_position = Point2(INFINITY, INFINITY);
+ }
+}
+
+void RuntimeNodeSelect::_click_point() {
+ Window *root = SceneTree::get_singleton()->get_root();
+ Point2 pos = root->get_screen_transform().affine_inverse().xform(selection_position);
+ Vector<SelectResult> items;
+
+ if (node_select_type == NODE_TYPE_2D) {
+ for (int i = 0; i < root->get_child_count(); i++) {
+ _find_canvas_items_at_pos(pos, root->get_child(i), items);
+ }
+
+ // Remove possible duplicates.
+ for (int i = 0; i < items.size(); i++) {
+ Node *item = items[i].item;
+ for (int j = 0; j < i; j++) {
+ if (items[j].item == item) {
+ items.remove_at(i);
+ i--;
+
+ break;
+ }
+ }
+ }
+ } else if (node_select_type == NODE_TYPE_3D) {
+#ifndef _3D_DISABLED
+ _find_3d_items_at_pos(pos, items);
+#endif // _3D_DISABLED
+ }
+
+ if (items.is_empty()) {
+ return;
+ }
+
+ items.sort();
+
+ if ((!list_shortcut_pressed && node_select_mode == SELECT_MODE_SINGLE) || items.size() == 1) {
+ Array message;
+ message.append(items[0].item->get_instance_id());
+ EngineDebugger::get_singleton()->send_message("remote_node_clicked", message);
+ } else if (list_shortcut_pressed || node_select_mode == SELECT_MODE_LIST) {
+ if (!selection_list->is_inside_tree()) {
+ root->add_child(selection_list);
+ }
+
+ selection_list->clear();
+ for (const SelectResult &I : items) {
+ selection_list->add_item(I.item->get_name());
+ selection_list->set_item_metadata(-1, I.item);
+ }
+
+ selection_list->set_position(selection_list->is_embedded() ? pos : selection_position + root->get_position());
+ selection_list->reset_size();
+ selection_list->popup();
+ // FIXME: Ugly hack that stops the popup from hiding when the button is released.
+ selection_list->call_deferred(SNAME("set_position"), selection_list->get_position() + Point2(1, 0));
+ }
+}
+
+void RuntimeNodeSelect::_select_node(Node *p_node) {
+ if (p_node == selected_node) {
+ return;
+ }
+
+ _clear_selection();
+
+ CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);
+ if (ci) {
+ selected_node = p_node;
+ } else {
+#ifndef _3D_DISABLED
+ Node3D *node_3d = Object::cast_to<Node3D>(p_node);
+ if (node_3d) {
+ if (!node_3d->is_inside_world()) {
+ return;
+ }
+
+ selected_node = p_node;
+
+ sbox_3d_instance = RS::get_singleton()->instance_create2(sbox_3d_mesh->get_rid(), node_3d->get_world_3d()->get_scenario());
+ sbox_3d_instance_ofs = RS::get_singleton()->instance_create2(sbox_3d_mesh->get_rid(), node_3d->get_world_3d()->get_scenario());
+ RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sbox_3d_instance, RS::SHADOW_CASTING_SETTING_OFF);
+ RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sbox_3d_instance_ofs, RS::SHADOW_CASTING_SETTING_OFF);
+ RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
+ RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
+ RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_ofs, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
+ RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_ofs, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
+
+ sbox_3d_instance_xray = RS::get_singleton()->instance_create2(sbox_3d_mesh_xray->get_rid(), node_3d->get_world_3d()->get_scenario());
+ sbox_3d_instance_xray_ofs = RS::get_singleton()->instance_create2(sbox_3d_mesh_xray->get_rid(), node_3d->get_world_3d()->get_scenario());
+ RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sbox_3d_instance_xray, RS::SHADOW_CASTING_SETTING_OFF);
+ RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sbox_3d_instance_xray_ofs, RS::SHADOW_CASTING_SETTING_OFF);
+ RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_xray, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
+ RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_xray, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
+ RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_xray_ofs, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
+ RS::get_singleton()->instance_geometry_set_flag(sbox_3d_instance_xray_ofs, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
+ }
+#endif // _3D_DISABLED
+ }
+
+ has_selection = selected_node;
+ _queue_selection_update();
+}
+
+void RuntimeNodeSelect::_queue_selection_update() {
+ if (has_selection && selection_visible) {
+ if (SceneTree::get_singleton()->is_suspended()) {
+ _update_selection();
+ } else {
+ selection_update_queued = true;
+ }
+ }
+}
+
+void RuntimeNodeSelect::_update_selection() {
+ if (has_selection && (!selected_node || !selected_node->is_inside_tree())) {
+ _clear_selection();
+ return;
+ }
+
+ CanvasItem *ci = Object::cast_to<CanvasItem>(selected_node);
+ if (ci) {
+ Window *root = SceneTree::get_singleton()->get_root();
+ Transform2D xform;
+ if (root->is_canvas_transform_override_enabled() && !ci->get_canvas_layer_node()) {
+ RS::get_singleton()->canvas_item_set_transform(sbox_2d_ci, (root->get_canvas_transform_override()));
+ xform = ci->get_global_transform();
+ } else {
+ RS::get_singleton()->canvas_item_set_transform(sbox_2d_ci, Transform2D());
+ xform = ci->get_global_transform_with_canvas();
+ }
+
+ // Fallback.
+ Rect2 rect = Rect2(Vector2(), Vector2(10, 10));
+
+ if (ci->_edit_use_rect()) {
+ rect = ci->_edit_get_rect();
+ } else {
+ CollisionShape2D *collision_shape = Object::cast_to<CollisionShape2D>(ci);
+ if (collision_shape) {
+ Ref<Shape2D> shape = collision_shape->get_shape();
+ if (shape.is_valid()) {
+ rect = shape->get_rect();
+ }
+ }
+ }
+
+ RS::get_singleton()->canvas_item_set_visible(sbox_2d_ci, selection_visible);
+
+ if (xform == sbox_2d_xform && rect == sbox_2d_rect) {
+ return; // Nothing changed.
+ }
+ sbox_2d_xform = xform;
+ sbox_2d_rect = rect;
+
+ RS::get_singleton()->canvas_item_clear(sbox_2d_ci);
+
+ const Vector2 endpoints[4] = {
+ xform.xform(rect.position),
+ xform.xform(rect.position + Vector2(rect.size.x, 0)),
+ xform.xform(rect.position + rect.size),
+ xform.xform(rect.position + Vector2(0, rect.size.y))
+ };
+
+ const Color selection_color_2d = Color(1, 0.6, 0.4, 0.7);
+ for (int i = 0; i < 4; i++) {
+ RS::get_singleton()->canvas_item_add_line(sbox_2d_ci, endpoints[i], endpoints[(i + 1) % 4], selection_color_2d, Math::round(2.f));
+ }
+ } else {
+#ifndef _3D_DISABLED
+ Node3D *node_3d = Object::cast_to<Node3D>(selected_node);
+
+ // Fallback.
+ AABB bounds(Vector3(-0.5, -0.5, -0.5), Vector3(1, 1, 1));
+
+ VisualInstance3D *visual_instance = Object::cast_to<VisualInstance3D>(node_3d);
+ if (visual_instance) {
+ bounds = visual_instance->get_aabb();
+ } else {
+ CollisionShape3D *collision_shape = Object::cast_to<CollisionShape3D>(node_3d);
+ if (collision_shape) {
+ Ref<Shape3D> shape = collision_shape->get_shape();
+ if (shape.is_valid()) {
+ bounds = shape->get_debug_mesh()->get_aabb();
+ }
+ }
+ }
+
+ RS::get_singleton()->instance_set_visible(sbox_3d_instance, selection_visible);
+ RS::get_singleton()->instance_set_visible(sbox_3d_instance_ofs, selection_visible);
+ RS::get_singleton()->instance_set_visible(sbox_3d_instance_xray, selection_visible);
+ RS::get_singleton()->instance_set_visible(sbox_3d_instance_xray_ofs, selection_visible);
+
+ Transform3D xform_to_top_level_parent_space = node_3d->get_global_transform().affine_inverse() * node_3d->get_global_transform();
+ bounds = xform_to_top_level_parent_space.xform(bounds);
+ Transform3D t = node_3d->get_global_transform();
+
+ if (t == sbox_3d_xform && bounds == sbox_3d_bounds) {
+ return; // Nothing changed.
+ }
+ sbox_3d_xform = t;
+ sbox_3d_bounds = bounds;
+
+ Transform3D t_offset = t;
+
+ // Apply AABB scaling before item's global transform.
+ {
+ const Vector3 offset(0.005, 0.005, 0.005);
+ Basis aabb_s;
+ aabb_s.scale(bounds.size + offset);
+ t.translate_local(bounds.position - offset / 2);
+ t.basis = t.basis * aabb_s;
+ }
+ {
+ const Vector3 offset(0.01, 0.01, 0.01);
+ Basis aabb_s;
+ aabb_s.scale(bounds.size + offset);
+ t_offset.translate_local(bounds.position - offset / 2);
+ t_offset.basis = t_offset.basis * aabb_s;
+ }
+
+ RS::get_singleton()->instance_set_transform(sbox_3d_instance, t);
+ RS::get_singleton()->instance_set_transform(sbox_3d_instance_ofs, t_offset);
+ RS::get_singleton()->instance_set_transform(sbox_3d_instance_xray, t);
+ RS::get_singleton()->instance_set_transform(sbox_3d_instance_xray_ofs, t_offset);
+#endif // _3D_DISABLED
+ }
+}
+
+void RuntimeNodeSelect::_clear_selection() {
+ selected_node = nullptr;
+ has_selection = false;
+
+ if (sbox_2d_canvas.is_valid()) {
+ RS::get_singleton()->canvas_item_clear(sbox_2d_ci);
+ }
+
+#ifndef _3D_DISABLED
+ if (sbox_3d_instance.is_valid()) {
+ RS::get_singleton()->free(sbox_3d_instance);
+ RS::get_singleton()->free(sbox_3d_instance_ofs);
+ RS::get_singleton()->free(sbox_3d_instance_xray);
+ RS::get_singleton()->free(sbox_3d_instance_xray_ofs);
+ }
+#endif // _3D_DISABLED
+}
+
+void RuntimeNodeSelect::_set_selection_visible(bool p_visible) {
+ selection_visible = p_visible;
+
+ if (has_selection) {
+ _update_selection();
+ }
+}
+
+// Copied and trimmed from the CanvasItemEditor implementation.
+void RuntimeNodeSelect::_find_canvas_items_at_pos(const Point2 &p_pos, Node *p_node, Vector<SelectResult> &r_items, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) {
+ if (!p_node || Object::cast_to<Viewport>(p_node)) {
+ return;
+ }
+
+ // In the original CanvasItemEditor, this value would be fetched from the "editors/polygon_editor/point_grab_radius" editor property,
+ // but since this is not accessible from here, we will just use the default value.
+ const real_t grab_distance = 8;
+ CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);
+
+ for (int i = p_node->get_child_count() - 1; i >= 0; i--) {
+ if (ci) {
+ if (!ci->is_set_as_top_level()) {
+ _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, p_parent_xform * ci->get_transform(), p_canvas_xform);
+ } else {
+ _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, ci->get_transform(), p_canvas_xform);
+ }
+ } else {
+ CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node);
+ _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, Transform2D(), cl ? cl->get_transform() : p_canvas_xform);
+ }
+ }
+
+ if (ci && ci->is_visible_in_tree()) {
+ Transform2D xform = p_canvas_xform;
+ if (!ci->is_set_as_top_level()) {
+ xform *= p_parent_xform;
+ }
+
+ Vector2 pos;
+ // Cameras (overridden or not) don't affect `CanvasLayer`s.
+ if (!ci->get_canvas_layer_node()) {
+ Window *root = SceneTree::get_singleton()->get_root();
+ pos = (root->is_canvas_transform_override_enabled() ? root->get_canvas_transform_override() : root->get_canvas_transform()).affine_inverse().xform(p_pos);
+ } else {
+ pos = p_pos;
+ }
+
+ xform = (xform * ci->get_transform()).affine_inverse();
+ const real_t local_grab_distance = xform.basis_xform(Vector2(grab_distance, 0)).length() / view_2d_zoom;
+ if (ci->_edit_is_selected_on_click(xform.xform(pos), local_grab_distance)) {
+ SelectResult res;
+ res.item = ci;
+ res.order = ci->get_effective_z_index() + ci->get_canvas_layer();
+ r_items.push_back(res);
+
+ // If it's a shape, get the collision object it's from.
+ // FIXME: If the collision object has multiple shapes, only the topmost will be above it in the list.
+ if (Object::cast_to<CollisionShape2D>(ci) || Object::cast_to<CollisionPolygon2D>(ci)) {
+ CollisionObject2D *collision_object = Object::cast_to<CollisionObject2D>(ci->get_parent());
+ if (collision_object) {
+ SelectResult res_col;
+ res_col.item = ci->get_parent();
+ res_col.order = collision_object->get_z_index() + ci->get_canvas_layer();
+ r_items.push_back(res_col);
+ }
+ }
+ }
+ }
+}
+
+void RuntimeNodeSelect::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
+ view_2d_offset.x -= p_scroll_vec.x / view_2d_zoom;
+ view_2d_offset.y -= p_scroll_vec.y / view_2d_zoom;
+
+ _update_view_2d();
+}
+
+// A very shallow copy of the same function inside CanvasItemEditor.
+void RuntimeNodeSelect::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
+ real_t prev_zoom = view_2d_zoom;
+ view_2d_zoom = CLAMP(view_2d_zoom * p_zoom_factor, VIEW_2D_MIN_ZOOM, VIEW_2D_MAX_ZOOM);
+
+ Vector2 pos = SceneTree::get_singleton()->get_root()->get_screen_transform().affine_inverse().xform(p_origin);
+ view_2d_offset += pos / prev_zoom - pos / view_2d_zoom;
+
+ // We want to align in-scene pixels to screen pixels, this prevents blurry rendering
+ // of small details (texts, lines).
+ // This correction adds a jitter movement when zooming, so we correct only when the
+ // zoom factor is an integer. (in the other cases, all pixels won't be aligned anyway)
+ const real_t closest_zoom_factor = Math::round(view_2d_zoom);
+ if (Math::is_zero_approx(view_2d_zoom - closest_zoom_factor)) {
+ // Make sure scene pixel at view_offset is aligned on a screen pixel.
+ Vector2 view_offset_int = view_2d_offset.floor();
+ Vector2 view_offset_frac = view_2d_offset - view_offset_int;
+ view_2d_offset = view_offset_int + (view_offset_frac * closest_zoom_factor).round() / closest_zoom_factor;
+ }
+
+ _update_view_2d();
+}
+
+void RuntimeNodeSelect::_reset_camera_2d() {
+ view_2d_offset = -SceneTree::get_singleton()->get_root()->get_canvas_transform().get_origin();
+ view_2d_zoom = 1;
+
+ _update_view_2d();
+}
+
+void RuntimeNodeSelect::_update_view_2d() {
+ Transform2D transform = Transform2D();
+ transform.scale_basis(Size2(view_2d_zoom, view_2d_zoom));
+ transform.columns[2] = -view_2d_offset * view_2d_zoom;
+
+ SceneTree::get_singleton()->get_root()->set_canvas_transform_override(transform);
+
+ _queue_selection_update();
+}
+
+#ifndef _3D_DISABLED
+void RuntimeNodeSelect::_find_3d_items_at_pos(const Point2 &p_pos, Vector<SelectResult> &r_items) {
+ Window *root = SceneTree::get_singleton()->get_root();
+ Camera3D *camera = root->get_viewport()->get_camera_3d();
+ if (!camera) {
+ return;
+ }
+
+ Vector3 ray, pos, to;
+ if (root->get_viewport()->is_camera_3d_override_enabled()) {
+ Viewport *vp = root->get_viewport();
+ ray = vp->camera_3d_override_project_ray_normal(p_pos);
+ pos = vp->camera_3d_override_project_ray_origin(p_pos);
+ to = pos + ray * vp->get_camera_3d_override_properties()["z_far"];
+ } else {
+ ray = camera->project_ray_normal(p_pos);
+ pos = camera->project_ray_origin(p_pos);
+ to = pos + ray * camera->get_far();
+ }
+
+ // Start with physical objects.
+ PhysicsDirectSpaceState3D *ss = root->get_world_3d()->get_direct_space_state();
+ PhysicsDirectSpaceState3D::RayResult result;
+ HashSet<RID> excluded;
+ PhysicsDirectSpaceState3D::RayParameters ray_params;
+ ray_params.from = pos;
+ ray_params.to = to;
+ ray_params.collide_with_areas = true;
+ while (true) {
+ ray_params.exclude = excluded;
+ if (ss->intersect_ray(ray_params, result)) {
+ SelectResult res;
+ res.item = Object::cast_to<Node>(result.collider);
+ res.order = -pos.distance_to(Object::cast_to<Node3D>(res.item)->get_global_transform().xform(result.position));
+
+ // Fetch collision shapes.
+ CollisionObject3D *collision = Object::cast_to<CollisionObject3D>(result.collider);
+ if (collision) {
+ List<uint32_t> owners;
+ collision->get_shape_owners(&owners);
+ for (const uint32_t &I : owners) {
+ SelectResult res_shape;
+ res_shape.item = Object::cast_to<Node>(collision->shape_owner_get_owner(I));
+ res_shape.order = res.order;
+ r_items.push_back(res_shape);
+ }
+ }
+
+ r_items.push_back(res);
+
+ excluded.insert(result.rid);
+ } else {
+ break;
+ }
+ }
+
+ // Then go for the meshes.
+ Vector<ObjectID> items = RS::get_singleton()->instances_cull_ray(pos, to, root->get_world_3d()->get_scenario());
+ for (int i = 0; i < items.size(); i++) {
+ Object *obj = ObjectDB::get_instance(items[i]);
+ GeometryInstance3D *geo_instance = nullptr;
+ Ref<TriangleMesh> mesh_collision;
+
+ MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(obj);
+ if (mesh_instance) {
+ if (mesh_instance->get_mesh().is_valid()) {
+ geo_instance = mesh_instance;
+ mesh_collision = mesh_instance->get_mesh()->generate_triangle_mesh();
+ }
+ } else {
+ Label3D *label = Object::cast_to<Label3D>(obj);
+ if (label) {
+ geo_instance = label;
+ mesh_collision = label->generate_triangle_mesh();
+ } else {
+ Sprite3D *sprite = Object::cast_to<Sprite3D>(obj);
+ if (sprite) {
+ geo_instance = sprite;
+ mesh_collision = sprite->generate_triangle_mesh();
+ }
+ }
+ }
+
+ if (mesh_collision.is_valid()) {
+ Transform3D gt = geo_instance->get_global_transform();
+ Transform3D ai = gt.affine_inverse();
+ Vector3 point, normal;
+ if (mesh_collision->intersect_ray(ai.xform(pos), ai.basis.xform(ray).normalized(), point, normal)) {
+ SelectResult res;
+ res.item = Object::cast_to<Node>(obj);
+ res.order = -pos.distance_to(gt.xform(point));
+ r_items.push_back(res);
+
+ continue;
+ }
+ }
+
+ items.remove_at(i);
+ i--;
+ }
+}
+
+bool RuntimeNodeSelect::_handle_3d_input(const Ref<InputEvent> &p_event) {
+ Ref<InputEventMouseButton> b = p_event;
+
+ if (b.is_valid()) {
+ const real_t zoom_factor = 1.08 * b->get_factor();
+ switch (b->get_button_index()) {
+ case MouseButton::WHEEL_UP: {
+ if (!camera_freelook) {
+ _cursor_scale_distance(1.0 / zoom_factor);
+ }
+
+ return true;
+ } break;
+ case MouseButton::WHEEL_DOWN: {
+ if (!camera_freelook) {
+ _cursor_scale_distance(zoom_factor);
+ }
+
+ return true;
+ } break;
+ case MouseButton::RIGHT: {
+ _set_camera_freelook_enabled(b->is_pressed());
+ return true;
+ } break;
+ default: {
+ }
+ }
+ }
+
+ Ref<InputEventMouseMotion> m = p_event;
+
+ if (m.is_valid()) {
+ if (camera_freelook) {
+ _cursor_look(m);
+ } else if (m->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) {
+ if (m->is_shift_pressed()) {
+ _cursor_pan(m);
+ } else {
+ _cursor_orbit(m);
+ }
+ }
+
+ return true;
+ }
+
+ Ref<InputEventKey> k = p_event;
+
+ if (k.is_valid()) {
+ if (k->get_physical_keycode() == Key::ESCAPE) {
+ _set_camera_freelook_enabled(false);
+ return true;
+ } else if (k->is_ctrl_pressed()) {
+ switch (k->get_physical_keycode()) {
+ case Key::EQUAL: {
+ cursor.fov_scale = CLAMP(cursor.fov_scale - 0.05, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_perspective(CAMERA_BASE_FOV * cursor.fov_scale, CAMERA_ZNEAR, CAMERA_ZFAR);
+
+ return true;
+ } break;
+ case Key::MINUS: {
+ cursor.fov_scale = CLAMP(cursor.fov_scale + 0.05, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_perspective(CAMERA_BASE_FOV * cursor.fov_scale, CAMERA_ZNEAR, CAMERA_ZFAR);
+
+ return true;
+ } break;
+ case Key::KEY_0: {
+ cursor.fov_scale = 1;
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_perspective(CAMERA_BASE_FOV, CAMERA_ZNEAR, CAMERA_ZFAR);
+
+ return true;
+ } break;
+ default: {
+ }
+ }
+ }
+ }
+
+ // TODO: Handle magnify and pan input gestures.
+
+ return false;
+}
+
+void RuntimeNodeSelect::_set_camera_freelook_enabled(bool p_enabled) {
+ camera_freelook = p_enabled;
+
+ if (p_enabled) {
+ // Make sure eye_pos is synced, because freelook referential is eye pos rather than orbit pos
+ Vector3 forward = _get_cursor_transform().basis.xform(Vector3(0, 0, -1));
+ cursor.eye_pos = cursor.pos - cursor.distance * forward;
+
+ previous_mouse_position = SceneTree::get_singleton()->get_root()->get_mouse_position();
+
+ // Hide mouse like in an FPS (warping doesn't work).
+ Input::get_singleton()->set_mouse_mode_override(Input::MOUSE_MODE_CAPTURED);
+
+ } else {
+ // Restore mouse.
+ Input::get_singleton()->set_mouse_mode_override(Input::MOUSE_MODE_VISIBLE);
+
+ // Restore the previous mouse position when leaving freelook mode.
+ // This is done because leaving `Input.MOUSE_MODE_CAPTURED` will center the cursor
+ // due to OS limitations.
+ Input::get_singleton()->warp_mouse(previous_mouse_position);
+ }
+}
+
+void RuntimeNodeSelect::_cursor_scale_distance(real_t p_scale) {
+ real_t min_distance = MAX(CAMERA_ZNEAR * 4, VIEW_3D_MIN_ZOOM);
+ real_t max_distance = MIN(CAMERA_ZFAR / 4, VIEW_3D_MAX_ZOOM);
+ cursor.distance = CLAMP(cursor.distance * p_scale, min_distance, max_distance);
+
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform());
+}
+
+void RuntimeNodeSelect::_cursor_look(Ref<InputEventWithModifiers> p_event) {
+ Window *root = SceneTree::get_singleton()->get_root();
+ const Vector2 relative = Input::get_singleton()->warp_mouse_motion(p_event, Rect2(Vector2(), root->get_size()));
+ const Transform3D prev_camera_transform = _get_cursor_transform();
+
+ cursor.x_rot += relative.y * RADS_PER_PIXEL;
+ // Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
+ cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);
+
+ cursor.y_rot += relative.x * RADS_PER_PIXEL;
+
+ // Look is like the opposite of Orbit: the focus point rotates around the camera.
+ Transform3D camera_transform = _get_cursor_transform();
+ Vector3 pos = camera_transform.xform(Vector3(0, 0, 0));
+ Vector3 prev_pos = prev_camera_transform.xform(Vector3(0, 0, 0));
+ Vector3 diff = prev_pos - pos;
+ cursor.pos += diff;
+
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform());
+}
+
+void RuntimeNodeSelect::_cursor_pan(Ref<InputEventWithModifiers> p_event) {
+ Window *root = SceneTree::get_singleton()->get_root();
+ // Reduce all sides of the area by 1, so warping works when windows are maximized/fullscreen.
+ const Vector2 relative = Input::get_singleton()->warp_mouse_motion(p_event, Rect2(Vector2(1, 1), root->get_size() - Vector2(2, 2)));
+ const real_t pan_speed = 1 / 150.0;
+
+ Transform3D camera_transform;
+ camera_transform.translate_local(cursor.pos);
+ camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);
+ camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);
+
+ Vector3 translation(1 * -relative.x * pan_speed, relative.y * pan_speed, 0);
+ translation *= cursor.distance / 4;
+ camera_transform.translate_local(translation);
+ cursor.pos = camera_transform.origin;
+
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform());
+}
+
+void RuntimeNodeSelect::_cursor_orbit(Ref<InputEventWithModifiers> p_event) {
+ Window *root = SceneTree::get_singleton()->get_root();
+ // Reduce all sides of the area by 1, so warping works when windows are maximized/fullscreen.
+ const Vector2 relative = Input::get_singleton()->warp_mouse_motion(p_event, Rect2(Vector2(1, 1), root->get_size() - Vector2(2, 2)));
+
+ cursor.x_rot += relative.y * RADS_PER_PIXEL;
+ // Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
+ cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);
+
+ cursor.y_rot += relative.x * RADS_PER_PIXEL;
+
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform());
+}
+
+Transform3D RuntimeNodeSelect::_get_cursor_transform() {
+ Transform3D camera_transform;
+ camera_transform.translate_local(cursor.pos);
+ camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);
+ camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);
+ camera_transform.translate_local(0, 0, cursor.distance);
+
+ return camera_transform;
+}
+
+void RuntimeNodeSelect::_reset_camera_3d() {
+ camera_first_override = true;
+
+ Window *root = SceneTree::get_singleton()->get_root();
+ Camera3D *camera = root->get_camera_3d();
+ if (!camera) {
+ return;
+ }
+
+ cursor = Cursor();
+ Transform3D transform = camera->get_global_transform();
+ transform.translate_local(0, 0, -cursor.distance);
+ cursor.pos = transform.origin;
+
+ cursor.x_rot = -camera->get_global_rotation().x;
+ cursor.y_rot = -camera->get_global_rotation().y;
+
+ cursor.fov_scale = CLAMP(camera->get_fov() / CAMERA_BASE_FOV, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);
+
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_transform(_get_cursor_transform());
+ SceneTree::get_singleton()->get_root()->set_camera_3d_override_perspective(CAMERA_BASE_FOV * cursor.fov_scale, CAMERA_ZNEAR, CAMERA_ZFAR);
+}
+#endif // _3D_DISABLED
#endif
diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h
index 70ea020c66..4429a00edc 100644
--- a/scene/debugger/scene_debugger.h
+++ b/scene/debugger/scene_debugger.h
@@ -33,19 +33,21 @@
#ifndef SCENE_DEBUGGER_H
#define SCENE_DEBUGGER_H
-#include "core/object/class_db.h"
+#include "core/input/shortcut.h"
#include "core/object/ref_counted.h"
#include "core/string/ustring.h"
#include "core/templates/pair.h"
#include "core/variant/array.h"
+#include "scene/gui/view_panner.h"
+#include "scene/resources/mesh.h"
+class PopupMenu;
class Script;
class Node;
class SceneDebugger {
-public:
private:
- static SceneDebugger *singleton;
+ inline static SceneDebugger *singleton = nullptr;
SceneDebugger();
@@ -61,6 +63,7 @@ private:
static void _set_node_owner_recursive(Node *p_node, Node *p_owner);
static void _set_object_property(ObjectID p_id, const String &p_property, const Variant &p_value);
static void _send_object_id(ObjectID p_id, int p_max_size = 1 << 20);
+ static void _next_frame();
public:
static Error parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured);
@@ -162,11 +165,161 @@ private:
live_edit_root = NodePath("/root");
}
- static LiveEditor *singleton;
+ inline static LiveEditor *singleton = nullptr;
public:
static LiveEditor *get_singleton();
};
+
+class RuntimeNodeSelect : public Object {
+ GDCLASS(RuntimeNodeSelect, Object);
+
+public:
+ enum NodeType {
+ NODE_TYPE_NONE,
+ NODE_TYPE_2D,
+ NODE_TYPE_3D,
+ NODE_TYPE_MAX
+ };
+
+ enum SelectMode {
+ SELECT_MODE_SINGLE,
+ SELECT_MODE_LIST,
+ SELECT_MODE_MAX
+ };
+
+private:
+ friend class SceneDebugger;
+
+ struct SelectResult {
+ Node *item = nullptr;
+ real_t order = 0;
+ _FORCE_INLINE_ bool operator<(const SelectResult &p_rr) const { return p_rr.order < order; }
+ };
+
+ bool has_selection = false;
+ Node *selected_node = nullptr;
+ PopupMenu *selection_list = nullptr;
+ bool selection_visible = true;
+ bool selection_update_queued = false;
+
+ bool camera_override = false;
+
+ // Values taken from EditorZoomWidget.
+ const float VIEW_2D_MIN_ZOOM = 1.0 / 128;
+ const float VIEW_2D_MAX_ZOOM = 128;
+
+ Ref<ViewPanner> panner;
+ Vector2 view_2d_offset;
+ real_t view_2d_zoom = 1.0;
+
+ RID sbox_2d_canvas;
+ RID sbox_2d_ci;
+ Transform2D sbox_2d_xform;
+ Rect2 sbox_2d_rect;
+
+#ifndef _3D_DISABLED
+ struct Cursor {
+ Vector3 pos;
+ real_t x_rot, y_rot, distance, fov_scale;
+ Vector3 eye_pos; // Used in freelook mode.
+
+ Cursor() {
+ // These rotations place the camera in +X +Y +Z, aka south east, facing north west.
+ x_rot = 0.5;
+ y_rot = -0.5;
+ distance = 4;
+ fov_scale = 1.0;
+ }
+ };
+ Cursor cursor;
+
+ // Values taken from Node3DEditor.
+ const float VIEW_3D_MIN_ZOOM = 0.01;
+#ifdef REAL_T_IS_DOUBLE
+ const double VIEW_3D_MAX_ZOOM = 1'000'000'000'000;
+#else
+ const float VIEW_3D_MAX_ZOOM = 10'000;
+#endif
+ const float CAMERA_ZNEAR = 0.05;
+ const float CAMERA_ZFAR = 4'000;
+
+ const float CAMERA_BASE_FOV = 75;
+ const float CAMERA_MIN_FOV_SCALE = 0.1;
+ const float CAMERA_MAX_FOV_SCALE = 2.5;
+
+ const float FREELOOK_BASE_SPEED = 4;
+ const float RADS_PER_PIXEL = 0.004;
+
+ bool camera_first_override = true;
+ bool camera_freelook = false;
+
+ Vector2 previous_mouse_position;
+
+ Ref<ArrayMesh> sbox_3d_mesh;
+ Ref<ArrayMesh> sbox_3d_mesh_xray;
+ RID sbox_3d_instance;
+ RID sbox_3d_instance_ofs;
+ RID sbox_3d_instance_xray;
+ RID sbox_3d_instance_xray_ofs;
+ Transform3D sbox_3d_xform;
+ AABB sbox_3d_bounds;
+#endif
+
+ Point2 selection_position = Point2(INFINITY, INFINITY);
+ bool list_shortcut_pressed = false;
+
+ NodeType node_select_type = NODE_TYPE_2D;
+ SelectMode node_select_mode = SELECT_MODE_SINGLE;
+
+ void _setup();
+
+ void _node_set_type(NodeType p_type);
+ void _select_set_mode(SelectMode p_mode);
+
+ void _set_camera_override_enabled(bool p_enabled);
+
+ void _root_window_input(const Ref<InputEvent> &p_event);
+ void _items_popup_index_pressed(int p_index, PopupMenu *p_popup);
+ void _update_input_state();
+
+ void _process_frame();
+ void _physics_frame();
+
+ void _click_point();
+ void _select_node(Node *p_node);
+ void _queue_selection_update();
+ void _update_selection();
+ void _clear_selection();
+ void _set_selection_visible(bool p_visible);
+
+ void _find_canvas_items_at_pos(const Point2 &p_pos, Node *p_node, Vector<SelectResult> &r_items, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D());
+ void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
+ void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
+ void _reset_camera_2d();
+ void _update_view_2d();
+
+#ifndef _3D_DISABLED
+ void _find_3d_items_at_pos(const Point2 &p_pos, Vector<SelectResult> &r_items);
+ bool _handle_3d_input(const Ref<InputEvent> &p_event);
+ void _set_camera_freelook_enabled(bool p_enabled);
+ void _cursor_scale_distance(real_t p_scale);
+ void _cursor_look(Ref<InputEventWithModifiers> p_event);
+ void _cursor_pan(Ref<InputEventWithModifiers> p_event);
+ void _cursor_orbit(Ref<InputEventWithModifiers> p_event);
+ Transform3D _get_cursor_transform();
+ void _reset_camera_3d();
+#endif
+
+ RuntimeNodeSelect() { singleton = this; }
+
+ inline static RuntimeNodeSelect *singleton = nullptr;
+
+public:
+ static RuntimeNodeSelect *get_singleton();
+
+ ~RuntimeNodeSelect();
+};
#endif
#endif // SCENE_DEBUGGER_H
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 10db8c387b..da59dac3b9 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -1483,7 +1483,7 @@ void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2
if (E) {
text_rid = E->value;
} else {
- String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding);
+ String fc = String::num_int64(p_line + 1).lpad(line_number_digits, line_number_padding);
if (is_localizing_numeral_system()) {
fc = TS->format_number(fc);
}
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 46ff710a6f..692933bbb4 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -69,7 +69,18 @@ void FileDialog::_native_popup() {
} else if (access == ACCESS_USERDATA) {
root = OS::get_singleton()->get_user_data_dir();
}
- DisplayServer::get_singleton()->file_dialog_with_options_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, _get_options(), callable_mp(this, &FileDialog::_native_dialog_cb));
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE_EXTRA)) {
+ DisplayServer::get_singleton()->file_dialog_with_options_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), root, file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, _get_options(), callable_mp(this, &FileDialog::_native_dialog_cb_with_options));
+ } else {
+ DisplayServer::get_singleton()->file_dialog_show(get_title(), ProjectSettings::get_singleton()->globalize_path(dir->get_text()), file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, callable_mp(this, &FileDialog::_native_dialog_cb));
+ }
+}
+
+bool FileDialog::_can_use_native_popup() {
+ if (access == ACCESS_RESOURCES || access == ACCESS_USERDATA || options.size() > 0) {
+ return DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE_EXTRA);
+ }
+ return DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE);
}
void FileDialog::popup(const Rect2i &p_rect) {
@@ -82,7 +93,7 @@ void FileDialog::popup(const Rect2i &p_rect) {
}
#endif
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
+ if (_can_use_native_popup() && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
_native_popup();
} else {
ConfirmationDialog::popup(p_rect);
@@ -101,7 +112,7 @@ void FileDialog::set_visible(bool p_visible) {
}
#endif
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
+ if (_can_use_native_popup() && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
if (p_visible) {
_native_popup();
}
@@ -110,7 +121,11 @@ void FileDialog::set_visible(bool p_visible) {
}
}
-void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options) {
+void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter) {
+ _native_dialog_cb_with_options(p_ok, p_files, p_filter, Dictionary());
+}
+
+void FileDialog::_native_dialog_cb_with_options(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options) {
if (!p_ok) {
file->set_text("");
emit_signal(SNAME("canceled"));
@@ -184,7 +199,7 @@ void FileDialog::_notification(int p_what) {
#endif
// Replace the built-in dialog with the native one if it started visible.
- if (is_visible() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
+ if (is_visible() && _can_use_native_popup() && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
ConfirmationDialog::set_visible(false);
_native_popup();
}
@@ -1489,7 +1504,7 @@ void FileDialog::set_use_native_dialog(bool p_native) {
#endif
// Replace the built-in dialog with the native one if it's currently visible.
- if (is_inside_tree() && is_visible() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
+ if (is_inside_tree() && is_visible() && _can_use_native_popup() && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
ConfirmationDialog::set_visible(false);
_native_popup();
}
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
index 98164263bc..84a3334503 100644
--- a/scene/gui/file_dialog.h
+++ b/scene/gui/file_dialog.h
@@ -190,8 +190,10 @@ private:
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
+ bool _can_use_native_popup();
void _native_popup();
- void _native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options);
+ void _native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter);
+ void _native_dialog_cb_with_options(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options);
bool _is_open_should_be_disabled();
diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp
index e35af24b34..ffa703d799 100644
--- a/scene/gui/video_stream_player.cpp
+++ b/scene/gui/video_stream_player.cpp
@@ -180,6 +180,7 @@ void VideoStreamPlayer::_notification(int p_notification) {
draw_texture_rect(texture, Rect2(Point2(), s), false);
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (is_playing() && !is_paused()) {
paused_from_tree = true;
@@ -191,6 +192,13 @@ void VideoStreamPlayer::_notification(int p_notification) {
}
} break;
+ case NOTIFICATION_UNSUSPENDED: {
+ if (get_tree()->is_paused()) {
+ break;
+ }
+ [[fallthrough]];
+ }
+
case NOTIFICATION_UNPAUSED: {
if (paused_from_tree) {
paused_from_tree = false;
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 8d0a3f831f..872757ba70 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -186,6 +186,7 @@ void Node::_notification(int p_notification) {
}
} break;
+ case NOTIFICATION_SUSPENDED:
case NOTIFICATION_PAUSED: {
if (is_physics_interpolated_and_enabled() && is_inside_tree()) {
reset_physics_interpolation();
@@ -701,6 +702,16 @@ void Node::_propagate_pause_notification(bool p_enable) {
data.blocked--;
}
+void Node::_propagate_suspend_notification(bool p_enable) {
+ notification(p_enable ? NOTIFICATION_SUSPENDED : NOTIFICATION_UNSUSPENDED);
+
+ data.blocked++;
+ for (KeyValue<StringName, Node *> &KV : data.children) {
+ KV.value->_propagate_suspend_notification(p_enable);
+ }
+ data.blocked--;
+}
+
Node::ProcessMode Node::get_process_mode() const {
return data.process_mode;
}
@@ -856,7 +867,7 @@ bool Node::can_process_notification(int p_what) const {
bool Node::can_process() const {
ERR_FAIL_COND_V(!is_inside_tree(), false);
- return _can_process(get_tree()->is_paused());
+ return !get_tree()->is_suspended() && _can_process(get_tree()->is_paused());
}
bool Node::_can_process(bool p_paused) const {
diff --git a/scene/main/node.h b/scene/main/node.h
index 33c42c487e..8b42e9ec6e 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -303,6 +303,7 @@ private:
void _set_tree(SceneTree *p_tree);
void _propagate_pause_notification(bool p_enable);
+ void _propagate_suspend_notification(bool p_enable);
_FORCE_INLINE_ bool _can_process(bool p_paused) const;
_FORCE_INLINE_ bool _is_enabled() const;
@@ -445,6 +446,8 @@ public:
// Editor specific node notifications
NOTIFICATION_EDITOR_PRE_SAVE = 9001,
NOTIFICATION_EDITOR_POST_SAVE = 9002,
+ NOTIFICATION_SUSPENDED = 9003,
+ NOTIFICATION_UNSUSPENDED = 9004
};
/* NODE/TREE */
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 675c91c766..5d6aa13a9b 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -956,11 +956,14 @@ Ref<ArrayMesh> SceneTree::get_debug_contact_mesh() {
void SceneTree::set_pause(bool p_enabled) {
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Pause can only be set from the main thread.");
+ ERR_FAIL_COND_MSG(suspended, "Pause state cannot be modified while suspended.");
if (p_enabled == paused) {
return;
}
+
paused = p_enabled;
+
#ifndef _3D_DISABLED
PhysicsServer3D::get_singleton()->set_active(!p_enabled);
#endif // _3D_DISABLED
@@ -974,6 +977,30 @@ bool SceneTree::is_paused() const {
return paused;
}
+void SceneTree::set_suspend(bool p_enabled) {
+ ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Suspend can only be set from the main thread.");
+
+ if (p_enabled == suspended) {
+ return;
+ }
+
+ suspended = p_enabled;
+
+ Engine::get_singleton()->set_freeze_time_scale(p_enabled);
+
+#ifndef _3D_DISABLED
+ PhysicsServer3D::get_singleton()->set_active(!p_enabled && !paused);
+#endif // _3D_DISABLED
+ PhysicsServer2D::get_singleton()->set_active(!p_enabled && !paused);
+ if (get_root()) {
+ get_root()->_propagate_suspend_notification(p_enabled);
+ }
+}
+
+bool SceneTree::is_suspended() const {
+ return suspended;
+}
+
void SceneTree::_process_group(ProcessGroup *p_group, bool p_physics) {
// When reading this function, keep in mind that this code must work in a way where
// if any node is removed, this needs to continue working.
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index d389ddd378..6fbdd4e234 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -145,6 +145,7 @@ private:
bool debug_navigation_hint = false;
#endif
bool paused = false;
+ bool suspended = false;
HashMap<StringName, Group> group_map;
bool _quit = false;
@@ -346,6 +347,8 @@ public:
void set_pause(bool p_enabled);
bool is_paused() const;
+ void set_suspend(bool p_enabled);
+ bool is_suspended() const;
#ifdef DEBUG_ENABLED
void set_debug_collisions_hint(bool p_enabled);
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 805cbf97d6..9734b1c260 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -3130,7 +3130,7 @@ void Viewport::push_input(const Ref<InputEvent> &p_event, bool p_local_coords) {
ERR_FAIL_COND(!is_inside_tree());
ERR_FAIL_COND(p_event.is_null());
- if (disable_input) {
+ if (disable_input || disable_input_override) {
return;
}
@@ -3202,7 +3202,7 @@ void Viewport::push_unhandled_input(const Ref<InputEvent> &p_event, bool p_local
local_input_handled = false;
- if (disable_input || !_can_consume_input_events()) {
+ if (disable_input || disable_input_override || !_can_consume_input_events()) {
return;
}
@@ -3305,7 +3305,7 @@ void Viewport::set_disable_input(bool p_disable) {
if (p_disable == disable_input) {
return;
}
- if (p_disable) {
+ if (p_disable && !disable_input_override) {
_drop_mouse_focus();
_mouse_leave_viewport();
_gui_cancel_tooltip();
@@ -3318,6 +3318,19 @@ bool Viewport::is_input_disabled() const {
return disable_input;
}
+void Viewport::set_disable_input_override(bool p_disable) {
+ ERR_MAIN_THREAD_GUARD;
+ if (p_disable == disable_input_override) {
+ return;
+ }
+ if (p_disable && !disable_input) {
+ _drop_mouse_focus();
+ _mouse_leave_viewport();
+ _gui_cancel_tooltip();
+ }
+ disable_input_override = p_disable;
+}
+
Variant Viewport::gui_get_drag_data() const {
ERR_READ_THREAD_GUARD_V(Variant());
return get_section_root_viewport()->gui.drag_data;
@@ -4244,6 +4257,22 @@ void Viewport::set_camera_3d_override_orthogonal(real_t p_size, real_t p_z_near,
}
}
+HashMap<StringName, real_t> Viewport::get_camera_3d_override_properties() const {
+ HashMap<StringName, real_t> props;
+
+ props["size"] = 0;
+ props["fov"] = 0;
+ props["z_near"] = 0;
+ props["z_far"] = 0;
+ ERR_READ_THREAD_GUARD_V(props);
+
+ props["size"] = camera_3d_override.size;
+ props["fov"] = camera_3d_override.fov;
+ props["z_near"] = camera_3d_override.z_near;
+ props["z_far"] = camera_3d_override.z_far;
+ return props;
+}
+
void Viewport::set_disable_3d(bool p_disable) {
ERR_MAIN_THREAD_GUARD;
disable_3d = p_disable;
@@ -4277,6 +4306,54 @@ Transform3D Viewport::get_camera_3d_override_transform() const {
return Transform3D();
}
+Vector3 Viewport::camera_3d_override_project_ray_normal(const Point2 &p_pos) const {
+ ERR_READ_THREAD_GUARD_V(Vector3());
+ Vector3 ray = camera_3d_override_project_local_ray_normal(p_pos);
+ return camera_3d_override.transform.basis.xform(ray).normalized();
+}
+
+Vector3 Viewport::camera_3d_override_project_local_ray_normal(const Point2 &p_pos) const {
+ ERR_READ_THREAD_GUARD_V(Vector3());
+ Size2 viewport_size = get_camera_rect_size();
+ Vector2 cpos = get_camera_coords(p_pos);
+ Vector3 ray;
+
+ if (camera_3d_override.projection == Camera3DOverrideData::PROJECTION_ORTHOGONAL) {
+ ray = Vector3(0, 0, -1);
+ } else {
+ Projection cm;
+ cm.set_perspective(camera_3d_override.fov, get_visible_rect().size.aspect(), camera_3d_override.z_near, camera_3d_override.z_far, false);
+
+ Vector2 screen_he = cm.get_viewport_half_extents();
+ ray = Vector3(((cpos.x / viewport_size.width) * 2.0 - 1.0) * screen_he.x, ((1.0 - (cpos.y / viewport_size.height)) * 2.0 - 1.0) * screen_he.y, -camera_3d_override.z_near).normalized();
+ }
+
+ return ray;
+}
+
+Vector3 Viewport::camera_3d_override_project_ray_origin(const Point2 &p_pos) const {
+ ERR_READ_THREAD_GUARD_V(Vector3());
+ Size2 viewport_size = get_camera_rect_size();
+ Vector2 cpos = get_camera_coords(p_pos);
+ ERR_FAIL_COND_V(viewport_size.y == 0, Vector3());
+
+ if (camera_3d_override.projection == Camera3DOverrideData::PROJECTION_ORTHOGONAL) {
+ Vector2 pos = cpos / viewport_size;
+ real_t vsize, hsize;
+ hsize = camera_3d_override.size * viewport_size.aspect();
+ vsize = camera_3d_override.size;
+
+ Vector3 ray;
+ ray.x = pos.x * (hsize)-hsize / 2;
+ ray.y = (1.0 - pos.y) * (vsize)-vsize / 2;
+ ray.z = -camera_3d_override.z_near;
+ ray = camera_3d_override.transform.xform(ray);
+ return ray;
+ } else {
+ return camera_3d_override.transform.origin;
+ };
+}
+
Ref<World3D> Viewport::get_world_3d() const {
ERR_READ_THREAD_GUARD_V(Ref<World3D>());
return world_3d;
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index d86ba9d826..31fbefe841 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -404,6 +404,7 @@ private:
DefaultCanvasItemTextureRepeat default_canvas_item_texture_repeat = DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_DISABLED;
bool disable_input = false;
+ bool disable_input_override = false;
void _gui_call_input(Control *p_control, const Ref<InputEvent> &p_input);
void _gui_call_notification(Control *p_control, int p_what);
@@ -583,6 +584,8 @@ public:
void set_disable_input(bool p_disable);
bool is_input_disabled() const;
+ void set_disable_input_override(bool p_disable);
+
Vector2 get_mouse_position() const;
void warp_mouse(const Vector2 &p_position);
virtual void update_mouse_cursor_state();
@@ -773,6 +776,11 @@ public:
void set_camera_3d_override_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_z_far);
void set_camera_3d_override_orthogonal(real_t p_size, real_t p_z_near, real_t p_z_far);
+ HashMap<StringName, real_t> get_camera_3d_override_properties() const;
+
+ Vector3 camera_3d_override_project_ray_normal(const Point2 &p_pos) const;
+ Vector3 camera_3d_override_project_ray_origin(const Point2 &p_pos) const;
+ Vector3 camera_3d_override_project_local_ray_normal(const Point2 &p_pos) const;
void set_disable_3d(bool p_disable);
bool is_3d_disabled() const;
diff --git a/scene/resources/3d/convex_polygon_shape_3d.cpp b/scene/resources/3d/convex_polygon_shape_3d.cpp
index dd52586b09..3e6db206ff 100644
--- a/scene/resources/3d/convex_polygon_shape_3d.cpp
+++ b/scene/resources/3d/convex_polygon_shape_3d.cpp
@@ -37,7 +37,7 @@
Vector<Vector3> ConvexPolygonShape3D::get_debug_mesh_lines() const {
Vector<Vector3> poly_points = get_points();
- if (poly_points.size() > 3) {
+ if (poly_points.size() > 1) { // Need at least 2 points for a line.
Vector<Vector3> varr = Variant(poly_points);
Geometry3D::MeshData md;
Error err = ConvexHullComputer::convex_hull(varr, md);
diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp
index 5badb7c61f..1658eeabef 100644
--- a/scene/resources/3d/importer_mesh.cpp
+++ b/scene/resources/3d/importer_mesh.cpp
@@ -35,108 +35,10 @@
#include "core/io/marshalls.h"
#include "core/math/convex_hull.h"
#include "core/math/random_pcg.h"
-#include "core/math/static_raycaster.h"
-#include "scene/resources/animation_library.h"
#include "scene/resources/surface_tool.h"
#include <cstdint>
-void ImporterMesh::Surface::split_normals(const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals) {
- _split_normals(arrays, p_indices, p_normals);
-
- for (BlendShape &blend_shape : blend_shape_data) {
- _split_normals(blend_shape.arrays, p_indices, p_normals);
- }
-}
-
-void ImporterMesh::Surface::_split_normals(Array &r_arrays, const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals) {
- ERR_FAIL_COND(r_arrays.size() != RS::ARRAY_MAX);
-
- const PackedVector3Array &vertices = r_arrays[RS::ARRAY_VERTEX];
- int current_vertex_count = vertices.size();
- int new_vertex_count = p_indices.size();
- int final_vertex_count = current_vertex_count + new_vertex_count;
- const int *indices_ptr = p_indices.ptr();
-
- for (int i = 0; i < r_arrays.size(); i++) {
- if (i == RS::ARRAY_INDEX) {
- continue;
- }
-
- if (r_arrays[i].get_type() == Variant::NIL) {
- continue;
- }
-
- switch (r_arrays[i].get_type()) {
- case Variant::PACKED_VECTOR3_ARRAY: {
- PackedVector3Array data = r_arrays[i];
- data.resize(final_vertex_count);
- Vector3 *data_ptr = data.ptrw();
- if (i == RS::ARRAY_NORMAL) {
- const Vector3 *normals_ptr = p_normals.ptr();
- memcpy(&data_ptr[current_vertex_count], normals_ptr, sizeof(Vector3) * new_vertex_count);
- } else {
- for (int j = 0; j < new_vertex_count; j++) {
- data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]];
- }
- }
- r_arrays[i] = data;
- } break;
- case Variant::PACKED_VECTOR2_ARRAY: {
- PackedVector2Array data = r_arrays[i];
- data.resize(final_vertex_count);
- Vector2 *data_ptr = data.ptrw();
- for (int j = 0; j < new_vertex_count; j++) {
- data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]];
- }
- r_arrays[i] = data;
- } break;
- case Variant::PACKED_FLOAT32_ARRAY: {
- PackedFloat32Array data = r_arrays[i];
- int elements = data.size() / current_vertex_count;
- data.resize(final_vertex_count * elements);
- float *data_ptr = data.ptrw();
- for (int j = 0; j < new_vertex_count; j++) {
- memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(float) * elements);
- }
- r_arrays[i] = data;
- } break;
- case Variant::PACKED_INT32_ARRAY: {
- PackedInt32Array data = r_arrays[i];
- int elements = data.size() / current_vertex_count;
- data.resize(final_vertex_count * elements);
- int32_t *data_ptr = data.ptrw();
- for (int j = 0; j < new_vertex_count; j++) {
- memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(int32_t) * elements);
- }
- r_arrays[i] = data;
- } break;
- case Variant::PACKED_BYTE_ARRAY: {
- PackedByteArray data = r_arrays[i];
- int elements = data.size() / current_vertex_count;
- data.resize(final_vertex_count * elements);
- uint8_t *data_ptr = data.ptrw();
- for (int j = 0; j < new_vertex_count; j++) {
- memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(uint8_t) * elements);
- }
- r_arrays[i] = data;
- } break;
- case Variant::PACKED_COLOR_ARRAY: {
- PackedColorArray data = r_arrays[i];
- data.resize(final_vertex_count);
- Color *data_ptr = data.ptrw();
- for (int j = 0; j < new_vertex_count; j++) {
- data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]];
- }
- r_arrays[i] = data;
- } break;
- default: {
- ERR_FAIL_MSG("Unhandled array type.");
- } break;
- }
- }
-}
-
String ImporterMesh::validate_blend_shape_name(const String &p_name) {
String name = p_name;
const char *characters = ":";
@@ -308,7 +210,7 @@ void ImporterMesh::optimize_indices_for_cache() {
} \
write_array[vert_idx] = transformed_vert;
-void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_bone_transform_array, bool p_raycast_normals) {
+void ImporterMesh::generate_lods(float p_normal_merge_angle, Array p_bone_transform_array) {
if (!SurfaceTool::simplify_scale_func) {
return;
}
@@ -381,8 +283,6 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
}
float normal_merge_threshold = Math::cos(Math::deg_to_rad(p_normal_merge_angle));
- float normal_pre_split_threshold = Math::cos(Math::deg_to_rad(MIN(180.0f, p_normal_split_angle * 2.0f)));
- float normal_split_threshold = Math::cos(Math::deg_to_rad(p_normal_split_angle));
const Vector3 *normals_ptr = normals.ptr();
HashMap<Vector3, LocalVector<Pair<int, int>>> unique_vertices;
@@ -471,22 +371,6 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
unsigned int index_target = 12; // Start with the smallest target, 4 triangles
unsigned int last_index_count = 0;
- // Only used for normal raycasting
- int split_vertex_count = vertex_count;
- LocalVector<Vector3> split_vertex_normals;
- LocalVector<int> split_vertex_indices;
- split_vertex_normals.reserve(index_count / 3);
- split_vertex_indices.reserve(index_count / 3);
-
- RandomPCG pcg;
- pcg.seed(123456789); // Keep seed constant across imports
-
- Ref<StaticRaycaster> raycaster = p_raycast_normals ? StaticRaycaster::create() : Ref<StaticRaycaster>();
- if (raycaster.is_valid()) {
- raycaster->add_mesh(vertices, indices, 0);
- raycaster->commit();
- }
-
const float max_mesh_error = FLT_MAX; // We don't want to limit by error, just by index target
float mesh_error = 0.0f;
@@ -536,173 +420,6 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
}
}
- if (raycaster.is_valid()) {
- LocalVector<LocalVector<int>> vertex_corners;
- vertex_corners.resize(vertex_count);
-
- int *ptrw = new_indices.ptrw();
- for (unsigned int j = 0; j < new_index_count; j++) {
- vertex_corners[ptrw[j]].push_back(j);
- }
-
- float error_factor = 1.0f / (scale * MAX(mesh_error, 0.15));
- const float ray_bias = 0.05;
- float ray_length = ray_bias + mesh_error * scale * 3.0f;
-
- Vector<StaticRaycaster::Ray> rays;
- LocalVector<Vector2> ray_uvs;
-
- int32_t *new_indices_ptr = new_indices.ptrw();
-
- int current_ray_count = 0;
- for (unsigned int j = 0; j < new_index_count; j += 3) {
- const Vector3 &v0 = vertices_ptr[new_indices_ptr[j + 0]];
- const Vector3 &v1 = vertices_ptr[new_indices_ptr[j + 1]];
- const Vector3 &v2 = vertices_ptr[new_indices_ptr[j + 2]];
- Vector3 face_normal = vec3_cross(v0 - v2, v0 - v1);
- float face_area = face_normal.length(); // Actually twice the face area, since it's the same error_factor on all faces, we don't care
- if (!Math::is_finite(face_area) || face_area == 0) {
- WARN_PRINT_ONCE("Ignoring face with non-finite normal in LOD generation.");
- continue;
- }
-
- Vector3 dir = face_normal / face_area;
- int ray_count = CLAMP(5.0 * face_area * error_factor, 16, 64);
-
- rays.resize(current_ray_count + ray_count);
- StaticRaycaster::Ray *rays_ptr = rays.ptrw();
-
- ray_uvs.resize(current_ray_count + ray_count);
- Vector2 *ray_uvs_ptr = ray_uvs.ptr();
-
- for (int k = 0; k < ray_count; k++) {
- float u = pcg.randf();
- float v = pcg.randf();
-
- if (u + v >= 1.0f) {
- u = 1.0f - u;
- v = 1.0f - v;
- }
-
- u = 0.9f * u + 0.05f / 3.0f; // Give barycentric coordinates some padding, we don't want to sample right on the edge
- v = 0.9f * v + 0.05f / 3.0f; // v = (v - one_third) * 0.95f + one_third;
- float w = 1.0f - u - v;
-
- Vector3 org = v0 * w + v1 * u + v2 * v;
- org -= dir * ray_bias;
- rays_ptr[current_ray_count + k] = StaticRaycaster::Ray(org, dir, 0.0f, ray_length);
- rays_ptr[current_ray_count + k].id = j / 3;
- ray_uvs_ptr[current_ray_count + k] = Vector2(u, v);
- }
-
- current_ray_count += ray_count;
- }
-
- raycaster->intersect(rays);
-
- LocalVector<Vector3> ray_normals;
- LocalVector<real_t> ray_normal_weights;
-
- ray_normals.resize(new_index_count);
- ray_normal_weights.resize(new_index_count);
-
- for (unsigned int j = 0; j < new_index_count; j++) {
- ray_normal_weights[j] = 0.0f;
- }
-
- const StaticRaycaster::Ray *rp = rays.ptr();
- for (int j = 0; j < rays.size(); j++) {
- if (rp[j].geomID != 0) { // Ray missed
- continue;
- }
-
- if (rp[j].normal.normalized().dot(rp[j].dir) > 0.0f) { // Hit a back face.
- continue;
- }
-
- const float &u = rp[j].u;
- const float &v = rp[j].v;
- const float w = 1.0f - u - v;
-
- const unsigned int &hit_tri_id = rp[j].primID;
- const unsigned int &orig_tri_id = rp[j].id;
-
- const Vector3 &n0 = normals_ptr[indices_ptr[hit_tri_id * 3 + 0]];
- const Vector3 &n1 = normals_ptr[indices_ptr[hit_tri_id * 3 + 1]];
- const Vector3 &n2 = normals_ptr[indices_ptr[hit_tri_id * 3 + 2]];
- Vector3 normal = n0 * w + n1 * u + n2 * v;
-
- Vector2 orig_uv = ray_uvs[j];
- const real_t orig_bary[3] = { 1.0f - orig_uv.x - orig_uv.y, orig_uv.x, orig_uv.y };
- for (int k = 0; k < 3; k++) {
- int idx = orig_tri_id * 3 + k;
- real_t weight = orig_bary[k];
- ray_normals[idx] += normal * weight;
- ray_normal_weights[idx] += weight;
- }
- }
-
- for (unsigned int j = 0; j < new_index_count; j++) {
- if (ray_normal_weights[j] < 1.0f) { // Not enough data, the new normal would be just a bad guess
- ray_normals[j] = Vector3();
- } else {
- ray_normals[j] /= ray_normal_weights[j];
- }
- }
-
- LocalVector<LocalVector<int>> normal_group_indices;
- LocalVector<Vector3> normal_group_averages;
- normal_group_indices.reserve(24);
- normal_group_averages.reserve(24);
-
- for (unsigned int j = 0; j < vertex_count; j++) {
- const LocalVector<int> &corners = vertex_corners[j];
- const Vector3 &vertex_normal = normals_ptr[j];
-
- for (const int &corner_idx : corners) {
- const Vector3 &ray_normal = ray_normals[corner_idx];
-
- if (ray_normal.length_squared() < CMP_EPSILON2) {
- continue;
- }
-
- bool found = false;
- for (unsigned int l = 0; l < normal_group_indices.size(); l++) {
- LocalVector<int> &group_indices = normal_group_indices[l];
- Vector3 n = normal_group_averages[l] / group_indices.size();
- if (n.dot(ray_normal) > normal_pre_split_threshold) {
- found = true;
- group_indices.push_back(corner_idx);
- normal_group_averages[l] += ray_normal;
- break;
- }
- }
-
- if (!found) {
- normal_group_indices.push_back({ corner_idx });
- normal_group_averages.push_back(ray_normal);
- }
- }
-
- for (unsigned int k = 0; k < normal_group_indices.size(); k++) {
- LocalVector<int> &group_indices = normal_group_indices[k];
- Vector3 n = normal_group_averages[k] / group_indices.size();
-
- if (vertex_normal.dot(n) < normal_split_threshold) {
- split_vertex_indices.push_back(j);
- split_vertex_normals.push_back(n);
- int new_idx = split_vertex_count++;
- for (const int &index : group_indices) {
- new_indices_ptr[index] = new_idx;
- }
- }
- }
-
- normal_group_indices.clear();
- normal_group_averages.clear();
- }
- }
-
Surface::LOD lod;
lod.distance = MAX(mesh_error * scale, CMP_EPSILON2);
lod.indices = new_indices;
@@ -715,22 +432,19 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
}
}
- if (raycaster.is_valid()) {
- surfaces.write[i].split_normals(split_vertex_indices, split_vertex_normals);
- }
-
surfaces.write[i].lods.sort_custom<Surface::LODComparator>();
for (int j = 0; j < surfaces.write[i].lods.size(); j++) {
Surface::LOD &lod = surfaces.write[i].lods.write[j];
unsigned int *lod_indices_ptr = (unsigned int *)lod.indices.ptrw();
- SurfaceTool::optimize_vertex_cache_func(lod_indices_ptr, lod_indices_ptr, lod.indices.size(), split_vertex_count);
+ SurfaceTool::optimize_vertex_cache_func(lod_indices_ptr, lod_indices_ptr, lod.indices.size(), vertex_count);
}
}
}
void ImporterMesh::_generate_lods_bind(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array) {
- generate_lods(p_normal_merge_angle, p_normal_split_angle, p_skin_pose_transform_array);
+ // p_normal_split_angle is unused, but kept for compatibility
+ generate_lods(p_normal_merge_angle, p_skin_pose_transform_array);
}
bool ImporterMesh::has_mesh() const {
diff --git a/scene/resources/3d/importer_mesh.h b/scene/resources/3d/importer_mesh.h
index fec474afb7..b4f3a03d27 100644
--- a/scene/resources/3d/importer_mesh.h
+++ b/scene/resources/3d/importer_mesh.h
@@ -70,9 +70,6 @@ class ImporterMesh : public Resource {
return l.distance < r.distance;
}
};
-
- void split_normals(const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals);
- static void _split_normals(Array &r_arrays, const LocalVector<int> &p_indices, const LocalVector<Vector3> &p_normals);
};
Vector<Surface> surfaces;
Vector<String> blend_shapes;
@@ -120,7 +117,7 @@ public:
void optimize_indices_for_cache();
- void generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array, bool p_raycast_normals = false);
+ void generate_lods(float p_normal_merge_angle, Array p_skin_pose_transform_array);
void create_shadow_mesh();
Ref<ImporterMesh> get_shadow_mesh() const;
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 86b01dae51..9f04ff6761 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -1562,7 +1562,7 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port
shader_code += " COLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ";\n";
} break;
case VisualShaderNode::PORT_TYPE_VECTOR_4D: {
- shader_code += " COLOR.rgb = n_out" + itos(p_node) + "p" + itos(p_port) + ".xyz;\n";
+ shader_code += " COLOR = n_out" + itos(p_node) + "p" + itos(p_port) + ";\n";
} break;
default: {
shader_code += " COLOR.rgb = vec3(0.0);\n";
diff --git a/scu_builders.py b/scu_builders.py
index 2d9af8a4e2..27def87443 100644
--- a/scu_builders.py
+++ b/scu_builders.py
@@ -290,11 +290,16 @@ def generate_scu_files(max_includes_per_scu):
process_folder(["drivers/unix"])
process_folder(["drivers/png"])
+ process_folder(["drivers/gles3/effects"])
+ process_folder(["drivers/gles3/storage"])
+
process_folder(["editor"], ["file_system_dock", "editor_resource_preview"], 32)
process_folder(["editor/debugger"])
process_folder(["editor/debugger/debug_adapter"])
process_folder(["editor/export"])
process_folder(["editor/gui"])
+ process_folder(["editor/themes"])
+ process_folder(["editor/project_manager"])
process_folder(["editor/import"])
process_folder(["editor/import/3d"])
process_folder(["editor/plugins"])
@@ -308,12 +313,15 @@ def generate_scu_files(max_includes_per_scu):
process_folder(["platform/web/export"])
process_folder(["platform/windows/export"])
+ process_folder(["modules/lightmapper_rd"])
process_folder(["modules/gltf"])
process_folder(["modules/gltf/structures"])
process_folder(["modules/gltf/editor"])
process_folder(["modules/gltf/extensions"])
process_folder(["modules/gltf/extensions/physics"])
process_folder(["modules/navigation"])
+ process_folder(["modules/navigation/2d"])
+ process_folder(["modules/navigation/3d"])
process_folder(["modules/webrtc"])
process_folder(["modules/websocket"])
process_folder(["modules/gridmap"])
@@ -322,6 +330,8 @@ def generate_scu_files(max_includes_per_scu):
process_folder(["modules/openxr"], ["register_types"])
process_folder(["modules/openxr/action_map"])
process_folder(["modules/openxr/editor"])
+ process_folder(["modules/openxr/extensions"])
+ process_folder(["modules/openxr/scene"])
process_folder(["modules/godot_physics_2d"])
process_folder(["modules/godot_physics_3d"])
process_folder(["modules/godot_physics_3d/joints"])
@@ -340,19 +350,26 @@ def generate_scu_files(max_includes_per_scu):
process_folder(["scene/animation"])
process_folder(["scene/gui"])
process_folder(["scene/main"])
+ process_folder(["scene/theme"])
process_folder(["scene/resources"])
process_folder(["scene/resources/2d"])
+ process_folder(["scene/resources/2d/skeleton"])
process_folder(["scene/resources/3d"])
process_folder(["servers"])
process_folder(["servers/rendering"])
+ process_folder(["servers/rendering/dummy/storage"])
process_folder(["servers/rendering/storage"])
process_folder(["servers/rendering/renderer_rd"])
process_folder(["servers/rendering/renderer_rd/effects"])
process_folder(["servers/rendering/renderer_rd/environment"])
process_folder(["servers/rendering/renderer_rd/storage_rd"])
+ process_folder(["servers/rendering/renderer_rd/forward_clustered"])
+ process_folder(["servers/rendering/renderer_rd/forward_mobile"])
process_folder(["servers/audio"])
process_folder(["servers/audio/effects"])
+ process_folder(["servers/navigation"])
+ process_folder(["servers/xr"])
# Finally change back the path to the calling folder
os.chdir(curr_folder)
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index 39558ad711..c893b96cee 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -1058,6 +1058,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(FEATURE_NATIVE_HELP);
BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_INPUT);
BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE);
+ BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE_EXTRA);
BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE);
BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN);
diff --git a/servers/display_server.h b/servers/display_server.h
index e50561deca..10695e5573 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -154,6 +154,7 @@ public:
FEATURE_NATIVE_HELP,
FEATURE_NATIVE_DIALOG_INPUT,
FEATURE_NATIVE_DIALOG_FILE,
+ FEATURE_NATIVE_DIALOG_FILE_EXTRA,
};
virtual bool has_feature(Feature p_feature) const = 0;
diff --git a/tests/core/math/test_aabb.h b/tests/core/math/test_aabb.h
index 9665bad1d7..5ce77c7fe7 100644
--- a/tests/core/math/test_aabb.h
+++ b/tests/core/math/test_aabb.h
@@ -50,7 +50,7 @@ TEST_CASE("[AABB] Constructor methods") {
TEST_CASE("[AABB] String conversion") {
CHECK_MESSAGE(
- String(AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6))) == "[P: (-1.5, 2, -2.5), S: (4, 5, 6)]",
+ String(AABB(Vector3(-1.5, 2, -2.5), Vector3(4, 5, 6))) == "[P: (-1.5, 2.0, -2.5), S: (4.0, 5.0, 6.0)]",
"The string representation should match the expected value.");
}
diff --git a/tests/core/math/test_color.h b/tests/core/math/test_color.h
index 988ec66f9d..591f6baac1 100644
--- a/tests/core/math/test_color.h
+++ b/tests/core/math/test_color.h
@@ -142,7 +142,7 @@ TEST_CASE("[Color] Conversion methods") {
cyan.to_rgba64() == 0x0000'ffff'ffff'ffff,
"The returned 64-bit BGR number should match the expected value.");
CHECK_MESSAGE(
- String(cyan) == "(0, 1, 1, 1)",
+ String(cyan) == "(0.0, 1.0, 1.0, 1.0)",
"The string representation should match the expected value.");
}
diff --git a/tests/core/math/test_rect2.h b/tests/core/math/test_rect2.h
index 2736d528ed..1c3c28fc80 100644
--- a/tests/core/math/test_rect2.h
+++ b/tests/core/math/test_rect2.h
@@ -59,7 +59,7 @@ TEST_CASE("[Rect2] Constructor methods") {
TEST_CASE("[Rect2] String conversion") {
// Note: This also depends on the Vector2 string representation.
CHECK_MESSAGE(
- String(Rect2(0, 100, 1280, 720)) == "[P: (0, 100), S: (1280, 720)]",
+ String(Rect2(0, 100, 1280, 720)) == "[P: (0.0, 100.0), S: (1280.0, 720.0)]",
"The string representation should match the expected value.");
}
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index 471a596ee9..d7dc7c99b9 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -457,9 +457,9 @@ TEST_CASE("[String] Erasing") {
}
TEST_CASE("[String] Number to string") {
- CHECK(String::num(0) == "0");
- CHECK(String::num(0.0) == "0"); // No trailing zeros.
- CHECK(String::num(-0.0) == "-0"); // Includes sign even for zero.
+ CHECK(String::num(0) == "0.0"); // The method takes double, so always add zeros.
+ CHECK(String::num(0.0) == "0.0");
+ CHECK(String::num(-0.0) == "-0.0"); // Includes sign even for zero.
CHECK(String::num(3.141593) == "3.141593");
CHECK(String::num(3.141593, 3) == "3.142");
CHECK(String::num(42.100023, 4) == "42.1"); // No trailing zeros.
@@ -494,15 +494,15 @@ TEST_CASE("[String] Number to string") {
CHECK(String::num_real(3.141593) == "3.141593");
CHECK(String::num_real(3.141) == "3.141"); // No trailing zeros.
#ifdef REAL_T_IS_DOUBLE
- CHECK_MESSAGE(String::num_real(123.456789) == "123.456789", "Prints the appropriate amount of digits for real_t = double.");
- CHECK_MESSAGE(String::num_real(-123.456789) == "-123.456789", "Prints the appropriate amount of digits for real_t = double.");
- CHECK_MESSAGE(String::num_real(Math_PI) == "3.14159265358979", "Prints the appropriate amount of digits for real_t = double.");
- CHECK_MESSAGE(String::num_real(3.1415f) == "3.1414999961853", "Prints more digits of 32-bit float when real_t = double (ones that would be reliable for double) and no trailing zero.");
+ CHECK_MESSAGE(String::num_real(real_t(123.456789)) == "123.456789", "Prints the appropriate amount of digits for real_t = double.");
+ CHECK_MESSAGE(String::num_real(real_t(-123.456789)) == "-123.456789", "Prints the appropriate amount of digits for real_t = double.");
+ CHECK_MESSAGE(String::num_real(real_t(Math_PI)) == "3.14159265358979", "Prints the appropriate amount of digits for real_t = double.");
+ CHECK_MESSAGE(String::num_real(real_t(3.1415f)) == "3.1414999961853", "Prints more digits of 32-bit float when real_t = double (ones that would be reliable for double) and no trailing zero.");
#else
- CHECK_MESSAGE(String::num_real(123.456789) == "123.4568", "Prints the appropriate amount of digits for real_t = float.");
- CHECK_MESSAGE(String::num_real(-123.456789) == "-123.4568", "Prints the appropriate amount of digits for real_t = float.");
- CHECK_MESSAGE(String::num_real(Math_PI) == "3.141593", "Prints the appropriate amount of digits for real_t = float.");
- CHECK_MESSAGE(String::num_real(3.1415f) == "3.1415", "Prints only reliable digits of 32-bit float when real_t = float.");
+ CHECK_MESSAGE(String::num_real(real_t(123.456789)) == "123.4568", "Prints the appropriate amount of digits for real_t = float.");
+ CHECK_MESSAGE(String::num_real(real_t(-123.456789)) == "-123.4568", "Prints the appropriate amount of digits for real_t = float.");
+ CHECK_MESSAGE(String::num_real(real_t(Math_PI)) == "3.141593", "Prints the appropriate amount of digits for real_t = float.");
+ CHECK_MESSAGE(String::num_real(real_t(3.1415f)) == "3.1415", "Prints only reliable digits of 32-bit float when real_t = float.");
#endif // REAL_T_IS_DOUBLE
// Checks doubles with many decimal places.
@@ -511,7 +511,7 @@ TEST_CASE("[String] Number to string") {
CHECK(String::num(-0.0000012345432123454321) == "-0.00000123454321");
CHECK(String::num(-10000.0000012345432123454321) == "-10000.0000012345");
CHECK(String::num(0.0000000000012345432123454321) == "0.00000000000123");
- CHECK(String::num(0.0000000000012345432123454321, 3) == "0");
+ CHECK(String::num(0.0000000000012345432123454321, 3) == "0.0");
// Note: When relevant (remainder > 0.5), the last digit gets rounded up,
// which can also lead to not include a trailing zero, e.g. "...89" -> "...9".
diff --git a/tests/core/variant/test_variant_utility.h b/tests/core/variant/test_variant_utility.h
index f25e8ac40d..5566380e75 100644
--- a/tests/core/variant/test_variant_utility.h
+++ b/tests/core/variant/test_variant_utility.h
@@ -91,7 +91,7 @@ TEST_CASE("[VariantUtility] Type conversion") {
converted = VariantUtilityFunctions::type_convert(basis, Variant::Type::STRING);
CHECK(converted.get_type() == Variant::Type::STRING);
- CHECK(converted == Variant("[X: (1.2, 0, 0), Y: (0, 3.4, 0), Z: (0, 0, 5.6)]"));
+ CHECK(converted == Variant("[X: (1.2, 0.0, 0.0), Y: (0.0, 3.4, 0.0), Z: (0.0, 0.0, 5.6)]"));
}
{