diff options
213 files changed, 1924 insertions, 734 deletions
diff --git a/core/config/engine.cpp b/core/config/engine.cpp index 7fdea7d1aa..6727c58fd1 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -239,6 +239,10 @@ bool Engine::is_validation_layers_enabled() const { return use_validation_layers; } +bool Engine::is_generate_spirv_debug_info_enabled() const { + return generate_spirv_debug_info; +} + void Engine::set_print_error_messages(bool p_enabled) { CoreGlobals::print_error_enabled = p_enabled; } diff --git a/core/config/engine.h b/core/config/engine.h index 5ea653ba6c..73d40d50ae 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -67,6 +67,7 @@ private: double _physics_interpolation_fraction = 0.0f; bool abort_on_gpu_errors = false; bool use_validation_layers = false; + bool generate_spirv_debug_info = false; int32_t gpu_idx = -1; uint64_t _process_frames = 0; @@ -156,6 +157,7 @@ public: bool is_abort_on_gpu_errors_enabled() const; bool is_validation_layers_enabled() const; + bool is_generate_spirv_debug_info_enabled() const; int32_t get_gpu_index() const; Engine(); diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index e8c885162b..1bfb745662 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -283,6 +283,7 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) { for (int i = 0; i < custom_feature_array.size(); i++) { custom_features.insert(custom_feature_array[i]); } + _queue_changed(); return true; } @@ -324,6 +325,7 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) { } } + _queue_changed(); return true; } @@ -424,6 +426,22 @@ void ProjectSettings::_get_property_list(List<PropertyInfo> *p_list) const { } } +void ProjectSettings::_queue_changed() { + if (is_changed || !MessageQueue::get_singleton() || MessageQueue::get_singleton()->get_max_buffer_usage() == 0) { + return; + } + is_changed = true; + callable_mp(this, &ProjectSettings::_emit_changed).call_deferred(); +} + +void ProjectSettings::_emit_changed() { + if (!is_changed) { + return; + } + is_changed = false; + emit_signal("settings_changed"); +} + bool ProjectSettings::_load_resource_pack(const String &p_pack, bool p_replace_files, int p_offset) { if (PackedData::get_singleton()->is_disabled()) { return false; @@ -1225,6 +1243,8 @@ void ProjectSettings::_bind_methods() { ClassDB::bind_method(D_METHOD("load_resource_pack", "pack", "replace_files", "offset"), &ProjectSettings::_load_resource_pack, DEFVAL(true), DEFVAL(0)); ClassDB::bind_method(D_METHOD("save_custom", "file"), &ProjectSettings::_save_custom_bnd); + + ADD_SIGNAL(MethodInfo("settings_changed")); } void ProjectSettings::_add_builtin_input_map() { @@ -1329,6 +1349,7 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/window/stretch/mode", PROPERTY_HINT_ENUM, "disabled,canvas_items,viewport"), "disabled"); GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/window/stretch/aspect", PROPERTY_HINT_ENUM, "ignore,keep,keep_width,keep_height,expand"), "keep"); GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "display/window/stretch/scale", PROPERTY_HINT_RANGE, "0.5,8.0,0.01"), 1.0); + GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/window/stretch/scale_mode", PROPERTY_HINT_ENUM, "fractional,integer"), "fractional"); GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/profiler/max_functions", PROPERTY_HINT_RANGE, "128,65535,1"), 16384); diff --git a/core/config/project_settings.h b/core/config/project_settings.h index a1f52fa1e0..dba4aa6822 100644 --- a/core/config/project_settings.h +++ b/core/config/project_settings.h @@ -45,6 +45,8 @@ class ProjectSettings : public Object { _THREAD_SAFE_CLASS_ friend class TestProjectSettingsInternalsAccessor; + bool is_changed = false; + public: typedef HashMap<String, Variant> CustomMap; static const String PROJECT_DATA_DIR_NAME_SUFFIX; @@ -115,6 +117,9 @@ protected: bool _property_can_revert(const StringName &p_name) const; bool _property_get_revert(const StringName &p_name, Variant &r_property) const; + void _queue_changed(); + void _emit_changed(); + static ProjectSettings *singleton; Error _load_settings_text(const String &p_path); diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 4e220d0839..05fe393a2f 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -929,6 +929,17 @@ Geometry3D *Geometry3D::get_singleton() { return singleton; } +Vector<Vector3> Geometry3D::compute_convex_mesh_points(const TypedArray<Plane> &p_planes) { + Vector<Plane> planes_vec; + int size = p_planes.size(); + planes_vec.resize(size); + for (int i = 0; i < size; ++i) { + planes_vec.set(i, p_planes[i]); + } + Variant ret = ::Geometry3D::compute_convex_mesh_points(planes_vec.ptr(), size); + return ret; +} + TypedArray<Plane> Geometry3D::build_box_planes(const Vector3 &p_extents) { Variant ret = ::Geometry3D::build_box_planes(p_extents); return ret; @@ -1029,6 +1040,7 @@ Vector<Vector3> Geometry3D::clip_polygon(const Vector<Vector3> &p_points, const } void Geometry3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("compute_convex_mesh_points", "planes"), &Geometry3D::compute_convex_mesh_points); ClassDB::bind_method(D_METHOD("build_box_planes", "extents"), &Geometry3D::build_box_planes); ClassDB::bind_method(D_METHOD("build_cylinder_planes", "radius", "height", "sides", "axis"), &Geometry3D::build_cylinder_planes, DEFVAL(Vector3::AXIS_Z)); ClassDB::bind_method(D_METHOD("build_capsule_planes", "radius", "height", "sides", "lats", "axis"), &Geometry3D::build_capsule_planes, DEFVAL(Vector3::AXIS_Z)); diff --git a/core/core_bind.h b/core/core_bind.h index 1cbbcdd251..5f51b64eb7 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -320,6 +320,7 @@ protected: public: static Geometry3D *get_singleton(); + Vector<Vector3> compute_convex_mesh_points(const TypedArray<Plane> &p_planes); TypedArray<Plane> build_box_planes(const Vector3 &p_extents); TypedArray<Plane> build_cylinder_planes(float p_radius, float p_height, int p_sides, Vector3::Axis p_axis = Vector3::AXIS_Z); TypedArray<Plane> build_capsule_planes(float p_radius, float p_height, int p_sides, int p_lats, Vector3::Axis p_axis = Vector3::AXIS_Z); diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 67b55db3db..1e4cd81034 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -485,6 +485,13 @@ void GDExtension::close_library() { ERR_FAIL_COND(library == nullptr); OS::get_singleton()->close_dynamic_library(library); +#if defined(TOOLS_ENABLED) && defined(WINDOWS_ENABLED) + // Delete temporary copy of library if it exists. + if (!temp_lib_path.is_empty() && Engine::get_singleton()->is_editor_hint()) { + DirAccess::remove_absolute(temp_lib_path); + } +#endif + library = nullptr; } @@ -607,12 +614,13 @@ Ref<Resource> GDExtensionResourceLoader::load(const String &p_path, const String } bool compatible = true; - if (VERSION_MAJOR < compatibility_minimum[0]) { - compatible = false; - } else if (VERSION_MINOR < compatibility_minimum[1]) { - compatible = false; - } else if (VERSION_PATCH < compatibility_minimum[2]) { - compatible = false; + // Check version lexicographically. + if (VERSION_MAJOR != compatibility_minimum[0]) { + compatible = VERSION_MAJOR > compatibility_minimum[0]; + } else if (VERSION_MINOR != compatibility_minimum[1]) { + compatible = VERSION_MINOR > compatibility_minimum[1]; + } else { + compatible = VERSION_PATCH >= compatibility_minimum[2]; } if (!compatible) { if (r_error) { @@ -640,6 +648,40 @@ Ref<Resource> GDExtensionResourceLoader::load(const String &p_path, const String Ref<GDExtension> lib; lib.instantiate(); String abs_path = ProjectSettings::get_singleton()->globalize_path(library_path); + +#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) + // If running on the editor on Windows, we copy the library and open the copy. + // This is so the original file isn't locked and can be updated by a compiler. + if (Engine::get_singleton()->is_editor_hint()) { + if (!FileAccess::exists(abs_path)) { + if (r_error) { + *r_error = ERR_FILE_NOT_FOUND; + } + ERR_PRINT("GDExtension library not found: " + library_path); + return Ref<Resource>(); + } + + // Copy the file to the same directory as the original with a prefix in the name. + // This is so relative path to dependencies are satisfied. + String copy_path = abs_path.get_base_dir().path_join("~" + abs_path.get_file()); + + Error copy_err = DirAccess::copy_absolute(abs_path, copy_path); + if (copy_err) { + if (r_error) { + *r_error = ERR_CANT_CREATE; + } + ERR_PRINT("Error copying GDExtension library: " + library_path); + return Ref<Resource>(); + } + FileAccess::set_hidden_attribute(copy_path, true); + + // Save the copied path so it can be deleted later. + lib->set_temp_library_path(copy_path); + + // Use the copy to open the library. + abs_path = copy_path; + } +#endif err = lib->open_library(abs_path, entry_symbol); if (r_error) { diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h index b935f8706f..5a4dd3d5f5 100644 --- a/core/extension/gdextension.h +++ b/core/extension/gdextension.h @@ -43,6 +43,9 @@ class GDExtension : public Resource { void *library = nullptr; // pointer if valid, String library_path; +#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) + String temp_lib_path; +#endif struct Extension { ObjectGDExtension gdextension; @@ -76,6 +79,10 @@ public: Error open_library(const String &p_path, const String &p_entry_symbol); void close_library(); +#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) + void set_temp_library_path(const String &p_path) { temp_lib_path = p_path; } +#endif + enum InitializationLevel { INITIALIZATION_LEVEL_CORE = GDEXTENSION_INITIALIZATION_CORE, INITIALIZATION_LEVEL_SERVERS = GDEXTENSION_INITIALIZATION_SERVERS, diff --git a/core/input/input.cpp b/core/input/input.cpp index 4a32abfafa..39f1acf623 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -475,7 +475,8 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, String p_name, S } joy_names[p_idx] = js; - emit_signal(SNAME("joy_connection_changed"), p_idx, p_connected); + // Ensure this signal is emitted on the main thread, as some platforms (e.g. Linux) call this from a different thread. + call_deferred("emit_signal", SNAME("joy_connection_changed"), p_idx, p_connected); } Vector3 Input::get_gravity() const { diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index b669afdc99..6026dbf896 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -583,7 +583,7 @@ uint64_t FileAccess::get_modified_time(const String &p_file) { return mt; } -uint32_t FileAccess::get_unix_permissions(const String &p_file) { +BitField<FileAccess::UnixPermissionFlags> FileAccess::get_unix_permissions(const String &p_file) { if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { return 0; } @@ -591,11 +591,10 @@ uint32_t FileAccess::get_unix_permissions(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 + "'."); - uint32_t mt = fa->_get_unix_permissions(p_file); - return mt; + return fa->_get_unix_permissions(p_file); } -Error FileAccess::set_unix_permissions(const String &p_file, uint32_t p_permissions) { +Error FileAccess::set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) { if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { return ERR_UNAVAILABLE; } @@ -607,6 +606,52 @@ Error FileAccess::set_unix_permissions(const String &p_file, uint32_t p_permissi return err; } +bool FileAccess::get_hidden_attribute(const String &p_file) { + if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { + return false; + } + + Ref<FileAccess> fa = create_for_path(p_file); + ERR_FAIL_COND_V_MSG(fa.is_null(), false, "Cannot create FileAccess for path '" + p_file + "'."); + + return fa->_get_hidden_attribute(p_file); +} + +Error FileAccess::set_hidden_attribute(const String &p_file, bool p_hidden) { + if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { + return ERR_UNAVAILABLE; + } + + 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 + "'."); + + Error err = fa->_set_hidden_attribute(p_file, p_hidden); + return err; +} + +bool FileAccess::get_read_only_attribute(const String &p_file) { + if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { + return false; + } + + Ref<FileAccess> fa = create_for_path(p_file); + ERR_FAIL_COND_V_MSG(fa.is_null(), false, "Cannot create FileAccess for path '" + p_file + "'."); + + return fa->_get_read_only_attribute(p_file); +} + +Error FileAccess::set_read_only_attribute(const String &p_file, bool p_ro) { + if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { + return ERR_UNAVAILABLE; + } + + 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 + "'."); + + Error err = fa->_set_read_only_attribute(p_file, p_ro); + return err; +} + void FileAccess::store_string(const String &p_string) { if (p_string.length() == 0) { return; @@ -865,6 +910,14 @@ void FileAccess::_bind_methods() { ClassDB::bind_static_method("FileAccess", D_METHOD("file_exists", "path"), &FileAccess::exists); ClassDB::bind_static_method("FileAccess", D_METHOD("get_modified_time", "file"), &FileAccess::get_modified_time); + ClassDB::bind_static_method("FileAccess", D_METHOD("get_unix_permissions", "file"), &FileAccess::get_unix_permissions); + ClassDB::bind_static_method("FileAccess", D_METHOD("set_unix_permissions", "file", "permissions"), &FileAccess::set_unix_permissions); + + ClassDB::bind_static_method("FileAccess", D_METHOD("get_hidden_attribute", "file"), &FileAccess::get_hidden_attribute); + ClassDB::bind_static_method("FileAccess", D_METHOD("set_hidden_attribute", "file", "hidden"), &FileAccess::set_hidden_attribute); + ClassDB::bind_static_method("FileAccess", D_METHOD("set_read_only_attribute", "file", "ro"), &FileAccess::set_read_only_attribute); + ClassDB::bind_static_method("FileAccess", D_METHOD("get_read_only_attribute", "file"), &FileAccess::get_read_only_attribute); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "big_endian"), "set_big_endian", "is_big_endian"); BIND_ENUM_CONSTANT(READ); @@ -877,4 +930,17 @@ void FileAccess::_bind_methods() { BIND_ENUM_CONSTANT(COMPRESSION_ZSTD); BIND_ENUM_CONSTANT(COMPRESSION_GZIP); BIND_ENUM_CONSTANT(COMPRESSION_BROTLI); + + BIND_BITFIELD_FLAG(UNIX_READ_OWNER); + BIND_BITFIELD_FLAG(UNIX_WRITE_OWNER); + BIND_BITFIELD_FLAG(UNIX_EXECUTE_OWNER); + BIND_BITFIELD_FLAG(UNIX_READ_GROUP); + BIND_BITFIELD_FLAG(UNIX_WRITE_GROUP); + BIND_BITFIELD_FLAG(UNIX_EXECUTE_GROUP); + BIND_BITFIELD_FLAG(UNIX_READ_OTHER); + BIND_BITFIELD_FLAG(UNIX_WRITE_OTHER); + BIND_BITFIELD_FLAG(UNIX_EXECUTE_OTHER); + BIND_BITFIELD_FLAG(UNIX_SET_USER_ID); + BIND_BITFIELD_FLAG(UNIX_SET_GROUP_ID); + BIND_BITFIELD_FLAG(UNIX_RESTRICTED_DELETE); } diff --git a/core/io/file_access.h b/core/io/file_access.h index ad1ac665f3..7b9e66bb83 100644 --- a/core/io/file_access.h +++ b/core/io/file_access.h @@ -60,6 +60,21 @@ public: WRITE_READ = 7, }; + enum UnixPermissionFlags { + UNIX_EXECUTE_OTHER = 0x001, + UNIX_WRITE_OTHER = 0x002, + UNIX_READ_OTHER = 0x004, + UNIX_EXECUTE_GROUP = 0x008, + UNIX_WRITE_GROUP = 0x010, + UNIX_READ_GROUP = 0x020, + UNIX_EXECUTE_OWNER = 0x040, + UNIX_WRITE_OWNER = 0x080, + UNIX_READ_OWNER = 0x100, + UNIX_RESTRICTED_DELETE = 0x200, + UNIX_SET_GROUP_ID = 0x400, + UNIX_SET_USER_ID = 0x800, + }; + enum CompressionMode { COMPRESSION_FASTLZ = Compression::MODE_FASTLZ, COMPRESSION_DEFLATE = Compression::MODE_DEFLATE, @@ -74,8 +89,13 @@ public: bool big_endian = false; bool real_is_double = false; - virtual uint32_t _get_unix_permissions(const String &p_file) = 0; - virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) = 0; + virtual BitField<UnixPermissionFlags> _get_unix_permissions(const String &p_file) = 0; + virtual Error _set_unix_permissions(const String &p_file, BitField<UnixPermissionFlags> p_permissions) = 0; + + virtual bool _get_hidden_attribute(const String &p_file) = 0; + virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) = 0; + virtual bool _get_read_only_attribute(const String &p_file) = 0; + virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) = 0; protected: static void _bind_methods(); @@ -185,8 +205,13 @@ public: static CreateFunc get_create_func(AccessType p_access); static bool exists(const String &p_name); ///< return true if a file exists static uint64_t get_modified_time(const String &p_file); - static uint32_t get_unix_permissions(const String &p_file); - static Error set_unix_permissions(const String &p_file, uint32_t p_permissions); + static BitField<FileAccess::UnixPermissionFlags> get_unix_permissions(const String &p_file); + static Error set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions); + + static bool get_hidden_attribute(const String &p_file); + static Error set_hidden_attribute(const String &p_file, bool p_hidden); + static bool get_read_only_attribute(const String &p_file); + static Error set_read_only_attribute(const String &p_file, bool p_ro); static void set_backup_save(bool p_enable) { backup_save = p_enable; }; static bool is_backup_save_enabled() { return backup_save; }; @@ -212,5 +237,6 @@ public: VARIANT_ENUM_CAST(FileAccess::CompressionMode); VARIANT_ENUM_CAST(FileAccess::ModeFlags); +VARIANT_BITFIELD_CAST(FileAccess::UnixPermissionFlags); #endif // FILE_ACCESS_H diff --git a/core/io/file_access_compressed.cpp b/core/io/file_access_compressed.cpp index 3e5a1217dd..0f00bd292c 100644 --- a/core/io/file_access_compressed.cpp +++ b/core/io/file_access_compressed.cpp @@ -365,20 +365,48 @@ uint64_t FileAccessCompressed::_get_modified_time(const String &p_file) { } } -uint32_t FileAccessCompressed::_get_unix_permissions(const String &p_file) { +BitField<FileAccess::UnixPermissionFlags> FileAccessCompressed::_get_unix_permissions(const String &p_file) { if (f.is_valid()) { return f->_get_unix_permissions(p_file); } return 0; } -Error FileAccessCompressed::_set_unix_permissions(const String &p_file, uint32_t p_permissions) { +Error FileAccessCompressed::_set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) { if (f.is_valid()) { return f->_set_unix_permissions(p_file, p_permissions); } return FAILED; } +bool FileAccessCompressed::_get_hidden_attribute(const String &p_file) { + if (f.is_valid()) { + return f->_get_hidden_attribute(p_file); + } + return false; +} + +Error FileAccessCompressed::_set_hidden_attribute(const String &p_file, bool p_hidden) { + if (f.is_valid()) { + return f->_set_hidden_attribute(p_file, p_hidden); + } + return FAILED; +} + +bool FileAccessCompressed::_get_read_only_attribute(const String &p_file) { + if (f.is_valid()) { + return f->_get_read_only_attribute(p_file); + } + return false; +} + +Error FileAccessCompressed::_set_read_only_attribute(const String &p_file, bool p_ro) { + if (f.is_valid()) { + return f->_set_read_only_attribute(p_file, p_ro); + } + return FAILED; +} + void FileAccessCompressed::close() { _close(); } diff --git a/core/io/file_access_compressed.h b/core/io/file_access_compressed.h index 601b74a9c1..bf57eaa07c 100644 --- a/core/io/file_access_compressed.h +++ b/core/io/file_access_compressed.h @@ -94,8 +94,13 @@ public: virtual bool file_exists(const String &p_name) override; ///< return true if a file exists virtual uint64_t _get_modified_time(const String &p_file) override; - virtual uint32_t _get_unix_permissions(const String &p_file) override; - virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) override; + virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override; + virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override; + + virtual bool _get_hidden_attribute(const String &p_file) override; + virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override; + virtual bool _get_read_only_attribute(const String &p_file) override; + virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override; virtual void close() override; diff --git a/core/io/file_access_encrypted.cpp b/core/io/file_access_encrypted.cpp index c39d19d52b..b689f5b628 100644 --- a/core/io/file_access_encrypted.cpp +++ b/core/io/file_access_encrypted.cpp @@ -285,13 +285,46 @@ uint64_t FileAccessEncrypted::_get_modified_time(const String &p_file) { return 0; } -uint32_t FileAccessEncrypted::_get_unix_permissions(const String &p_file) { +BitField<FileAccess::UnixPermissionFlags> FileAccessEncrypted::_get_unix_permissions(const String &p_file) { + if (file.is_valid()) { + return file->_get_unix_permissions(p_file); + } return 0; } -Error FileAccessEncrypted::_set_unix_permissions(const String &p_file, uint32_t p_permissions) { - ERR_PRINT("Setting UNIX permissions on encrypted files is not implemented yet."); - return ERR_UNAVAILABLE; +Error FileAccessEncrypted::_set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) { + if (file.is_valid()) { + return file->_set_unix_permissions(p_file, p_permissions); + } + return FAILED; +} + +bool FileAccessEncrypted::_get_hidden_attribute(const String &p_file) { + if (file.is_valid()) { + return file->_get_hidden_attribute(p_file); + } + return false; +} + +Error FileAccessEncrypted::_set_hidden_attribute(const String &p_file, bool p_hidden) { + if (file.is_valid()) { + return file->_set_hidden_attribute(p_file, p_hidden); + } + return FAILED; +} + +bool FileAccessEncrypted::_get_read_only_attribute(const String &p_file) { + if (file.is_valid()) { + return file->_get_read_only_attribute(p_file); + } + return false; +} + +Error FileAccessEncrypted::_set_read_only_attribute(const String &p_file, bool p_ro) { + if (file.is_valid()) { + return file->_set_read_only_attribute(p_file, p_ro); + } + return FAILED; } void FileAccessEncrypted::close() { diff --git a/core/io/file_access_encrypted.h b/core/io/file_access_encrypted.h index 9702b5a517..489d213b8f 100644 --- a/core/io/file_access_encrypted.h +++ b/core/io/file_access_encrypted.h @@ -85,8 +85,13 @@ public: virtual bool file_exists(const String &p_name) override; ///< return true if a file exists virtual uint64_t _get_modified_time(const String &p_file) override; - virtual uint32_t _get_unix_permissions(const String &p_file) override; - virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) override; + virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override; + virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override; + + virtual bool _get_hidden_attribute(const String &p_file) override; + virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override; + virtual bool _get_read_only_attribute(const String &p_file) override; + virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override; virtual void close() override; diff --git a/core/io/file_access_memory.h b/core/io/file_access_memory.h index 43fe6ab658..ac08e5406f 100644 --- a/core/io/file_access_memory.h +++ b/core/io/file_access_memory.h @@ -68,8 +68,13 @@ public: virtual bool file_exists(const String &p_name) override; ///< return true if a file exists virtual uint64_t _get_modified_time(const String &p_file) override { return 0; } - virtual uint32_t _get_unix_permissions(const String &p_file) override { return 0; } - virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) override { return FAILED; } + virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; } + virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override { return FAILED; } + + virtual bool _get_hidden_attribute(const String &p_file) override { return false; } + virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override { return ERR_UNAVAILABLE; } + virtual bool _get_read_only_attribute(const String &p_file) override { return false; } + virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override { return ERR_UNAVAILABLE; } virtual void close() override {} diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h index 1538b302c2..97391a5611 100644 --- a/core/io/file_access_pack.h +++ b/core/io/file_access_pack.h @@ -150,8 +150,13 @@ class FileAccessPack : public FileAccess { Ref<FileAccess> f; virtual Error open_internal(const String &p_path, int p_mode_flags) override; virtual uint64_t _get_modified_time(const String &p_file) override { return 0; } - virtual uint32_t _get_unix_permissions(const String &p_file) override { return 0; } - virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) override { return FAILED; } + virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; } + virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override { return FAILED; } + + virtual bool _get_hidden_attribute(const String &p_file) override { return false; } + virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override { return ERR_UNAVAILABLE; } + virtual bool _get_read_only_attribute(const String &p_file) override { return false; } + virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override { return ERR_UNAVAILABLE; } public: virtual bool is_open() const override; diff --git a/core/io/file_access_zip.h b/core/io/file_access_zip.h index f8b640946c..1062a06238 100644 --- a/core/io/file_access_zip.h +++ b/core/io/file_access_zip.h @@ -106,8 +106,13 @@ public: virtual bool file_exists(const String &p_name) override; ///< return true if a file exists virtual uint64_t _get_modified_time(const String &p_file) override { return 0; } // todo - virtual uint32_t _get_unix_permissions(const String &p_file) override { return 0; } - virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) override { return FAILED; } + virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; } + virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override { return FAILED; } + + virtual bool _get_hidden_attribute(const String &p_file) override { return false; } + virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override { return ERR_UNAVAILABLE; } + virtual bool _get_read_only_attribute(const String &p_file) override { return false; } + virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override { return ERR_UNAVAILABLE; } virtual void close() override; diff --git a/core/io/remote_filesystem_client.h b/core/io/remote_filesystem_client.h index 42eba98eb1..fcb5c1cfc3 100644 --- a/core/io/remote_filesystem_client.h +++ b/core/io/remote_filesystem_client.h @@ -44,8 +44,8 @@ protected: String _get_cache_path() { return cache_path; } struct FileCache { String path; // Local path (as in "folder/to/file.png") - uint64_t server_modified_time; // MD5 checksum. - uint64_t modified_time; + uint64_t server_modified_time = 0; // MD5 checksum. + uint64_t modified_time = 0; }; virtual bool _is_configured() { return !cache_path.is_empty(); } // Can be re-implemented per platform. If so, feel free to ignore get_cache_path() diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 12e6423724..80ca51573c 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -1750,7 +1750,7 @@ Vector<uint8_t> String::hex_decode() const { void String::print_unicode_error(const String &p_message, bool p_critical) const { if (p_critical) { - print_error(vformat("Unicode parsing error, some characters were replaced with � (U+FFFD): %s", p_message)); + print_error(vformat(U"Unicode parsing error, some characters were replaced with � (U+FFFD): %s", p_message)); } else { print_error(vformat("Unicode parsing error: %s", p_message)); } @@ -3635,6 +3635,23 @@ String String::repeat(int p_count) const { return new_string; } +String String::reverse() const { + int len = length(); + if (len <= 1) { + return *this; + } + String new_string; + new_string.resize(len + 1); + + const char32_t *src = ptr(); + char32_t *dst = new_string.ptrw(); + for (int i = 0; i < len; i++) { + dst[i] = src[len - i - 1]; + } + dst[len] = _null; + return new_string; +} + String String::left(int p_len) const { if (p_len < 0) { p_len = length() + p_len; diff --git a/core/string/ustring.h b/core/string/ustring.h index 295625395d..f45392eee1 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -305,6 +305,7 @@ public: String replace(const char *p_key, const char *p_with) const; String replacen(const String &p_key, const String &p_with) const; String repeat(int p_count) const; + String reverse() const; String insert(int p_at_pos, const String &p_string) const; String erase(int p_pos, int p_chars = 1) const; String pad_decimals(int p_digits) const; diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index dad9183216..ccf9b82022 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1659,6 +1659,7 @@ static void _register_variant_builtin_methods() { bind_string_methodv(replace, static_cast<String (String::*)(const String &, const String &) const>(&String::replace), sarray("what", "forwhat"), varray()); bind_string_method(replacen, sarray("what", "forwhat"), varray()); bind_string_method(repeat, sarray("count"), varray()); + bind_string_method(reverse, sarray(), varray()); bind_string_method(insert, sarray("position", "what"), varray()); bind_string_method(erase, sarray("position", "chars"), varray(1)); bind_string_method(capitalize, sarray(), varray()); diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 008452a92e..e44fdd9776 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -1463,6 +1463,10 @@ <member name="DisplayServer" type="DisplayServer" setter="" getter=""> The [DisplayServer] singleton. </member> + <member name="EditorInterface" type="EditorInterface" setter="" getter=""> + The [EditorInterface] singleton. + [b]Note:[/b] Only available in editor builds. + </member> <member name="Engine" type="Engine" setter="" getter=""> The [Engine] singleton. </member> diff --git a/doc/classes/BackBufferCopy.xml b/doc/classes/BackBufferCopy.xml index e4e6f1d76d..23d6bc3851 100644 --- a/doc/classes/BackBufferCopy.xml +++ b/doc/classes/BackBufferCopy.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="BackBufferCopy" inherits="Node2D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Copies a region of the screen (or the whole screen) to a buffer so it can be accessed in your shader scripts using the screen texture (i.e. a uniform sampler with [code]hint_screen_texture[/code]). + A node that copies a region of the screen to a buffer for access in shader code. </brief_description> <description> - Node for back-buffering the currently-displayed screen. The region defined in the [BackBufferCopy] node is buffered with the content of the screen it covers, or the entire screen according to the copy mode set. Use the screen texture in your shader scripts to access the buffer. + Node for back-buffering the currently-displayed screen. The region defined in the [BackBufferCopy] node is buffered with the content of the screen it covers, or the entire screen according to the [member copy_mode]. It can be accessed in shader scripts using the screen texture (i.e. a uniform sampler with [code]hint_screen_texture[/code]). [b]Note:[/b] Since this node inherits from [Node2D] (and not [Control]), anchors and margins won't apply to child [Control]-derived nodes. This can be problematic when resizing the window. To avoid this, add [Control]-derived nodes as [i]siblings[/i] to the [BackBufferCopy] node instead of adding them as children. </description> <tutorials> diff --git a/doc/classes/BaseMaterial3D.xml b/doc/classes/BaseMaterial3D.xml index 949dcc24d0..1cc2976c81 100644 --- a/doc/classes/BaseMaterial3D.xml +++ b/doc/classes/BaseMaterial3D.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="BaseMaterial3D" inherits="Material" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Default 3D rendering material. + Abstract base class for defining the 3D rendering properties of meshes. </brief_description> <description> - This provides a default material with a wide variety of rendering features and properties without the need to write shader code. See the tutorial below for details. + This class serves as a default material with a wide variety of rendering features and properties without the need to write shader code. See the tutorial below for details. </description> <tutorials> - <link title="Standard Material 3D">$DOCS_URL/tutorials/3d/standard_material_3d.html</link> + <link title="Standard Material 3D and ORM Material 3D">$DOCS_URL/tutorials/3d/standard_material_3d.html</link> </tutorials> <methods> <method name="get_feature" qualifiers="const"> diff --git a/doc/classes/Button.xml b/doc/classes/Button.xml index 6faefcbe0d..25c71deb1f 100644 --- a/doc/classes/Button.xml +++ b/doc/classes/Button.xml @@ -115,7 +115,7 @@ <theme_item name="icon_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> Icon modulate [Color] used when the [Button] is being pressed. </theme_item> - <theme_item name="h_separation" data_type="constant" type="int" default="2"> + <theme_item name="h_separation" data_type="constant" type="int" default="4"> The horizontal space between [Button]'s icon and text. Negative values will be treated as [code]0[/code] when used. </theme_item> <theme_item name="icon_max_width" data_type="constant" type="int" default="0"> diff --git a/doc/classes/CPUParticles2D.xml b/doc/classes/CPUParticles2D.xml index eaacdd590d..051635cb48 100644 --- a/doc/classes/CPUParticles2D.xml +++ b/doc/classes/CPUParticles2D.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CPUParticles2D" inherits="Node2D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - CPU-based 2D particle emitter. + A CPU-based 2D particle emitter. </brief_description> <description> CPU-based 2D particle node used to create a variety of particle systems and effects. diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml index f03c463049..bd096dc9c6 100644 --- a/doc/classes/CPUParticles3D.xml +++ b/doc/classes/CPUParticles3D.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CPUParticles3D" inherits="GeometryInstance3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - CPU-based 3D particle emitter. + A CPU-based 3D particle emitter. </brief_description> <description> CPU-based 3D particle node used to create a variety of particle systems and effects. diff --git a/doc/classes/Callable.xml b/doc/classes/Callable.xml index c7b2d6df94..b903e98319 100644 --- a/doc/classes/Callable.xml +++ b/doc/classes/Callable.xml @@ -138,7 +138,7 @@ <method name="get_method" qualifiers="const"> <return type="StringName" /> <description> - Returns the name of the method represented by this [Callable]. If the callable is a lambda function, returns the function's name. + Returns the name of the method represented by this [Callable]. If the callable is a GDScript lambda function, returns the function's name or [code]"<anonymous lambda>"[/code]. </description> </method> <method name="get_object" qualifiers="const"> diff --git a/doc/classes/ColorPickerButton.xml b/doc/classes/ColorPickerButton.xml index 32ec27868a..f6af188998 100644 --- a/doc/classes/ColorPickerButton.xml +++ b/doc/classes/ColorPickerButton.xml @@ -74,7 +74,7 @@ <theme_item name="font_pressed_color" data_type="color" type="Color" default="Color(0.8, 0.8, 0.8, 1)"> Text [Color] used when the [ColorPickerButton] is being pressed. </theme_item> - <theme_item name="h_separation" data_type="constant" type="int" default="2"> + <theme_item name="h_separation" data_type="constant" type="int" default="4"> The horizontal space between [ColorPickerButton]'s icon and text. </theme_item> <theme_item name="outline_size" data_type="constant" type="int" default="0"> diff --git a/doc/classes/CompressedCubemap.xml b/doc/classes/CompressedCubemap.xml index e72d727d7d..6ab0cc5d88 100644 --- a/doc/classes/CompressedCubemap.xml +++ b/doc/classes/CompressedCubemap.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CompressedCubemap" inherits="CompressedTextureLayered" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - 6-sided texture typically used in 3D rendering, optionally compressed. + An optionally compressed [Cubemap]. </brief_description> <description> A cubemap that is loaded from a [code].ccube[/code] file. This file format is internal to Godot; it is created by importing other image formats with the import system. [CompressedCubemap] can use one of 4 compresson methods: diff --git a/doc/classes/CompressedCubemapArray.xml b/doc/classes/CompressedCubemapArray.xml index f5829e4e3e..32687229ed 100644 --- a/doc/classes/CompressedCubemapArray.xml +++ b/doc/classes/CompressedCubemapArray.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CompressedCubemapArray" inherits="CompressedTextureLayered" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Array of 6-sided textures typically used in 3D rendering, optionally compressed. + An optionally compressed [CubemapArray]. </brief_description> <description> A cubemap array that is loaded from a [code].ccubearray[/code] file. This file format is internal to Godot; it is created by importing other image formats with the import system. [CompressedCubemapArray] can use one of 4 compresson methods: diff --git a/doc/classes/Cubemap.xml b/doc/classes/Cubemap.xml index 0d3b52ddfc..b7da3c4ec6 100644 --- a/doc/classes/Cubemap.xml +++ b/doc/classes/Cubemap.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Cubemap" inherits="ImageTextureLayered" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - 6-sided texture typically used in 3D rendering. + Six square textures representing the faces of a cube. Commonly used as a skybox. </brief_description> <description> A cubemap is made of 6 textures organized in layers. They are typically used for faking reflections in 3D rendering (see [ReflectionProbe]). It can be used to make an object look as if it's reflecting its surroundings. This usually delivers much better performance than other reflection methods. diff --git a/doc/classes/CubemapArray.xml b/doc/classes/CubemapArray.xml index ee4ec239f3..7ee497385e 100644 --- a/doc/classes/CubemapArray.xml +++ b/doc/classes/CubemapArray.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CubemapArray" inherits="ImageTextureLayered" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - A single composite texture resource which consists of multiple [Cubemap]s. + An array of [Cubemap]s, stored together and with a single reference. </brief_description> <description> - [CubemapArray]s are made of an array of [Cubemap]s. Accordingly, like [Cubemap]s they are made of multiple textures the amount of which must be divisible by 6 (one image for each face of the cube). The primary benefit of [CubemapArray]s is that they can be accessed in shader code using a single texture reference. In other words, you can pass multiple [Cubemap]s into a shader using a single [CubemapArray]. - Generally, [CubemapArray]s provide a more efficient way for storing multiple [Cubemap]s compared to storing multiple [Cubemap]s themselves in an array. - Internally, Godot uses [CubemapArray]s for many effects including the [Sky], if you set [member ProjectSettings.rendering/reflections/sky_reflections/texture_array_reflections] to [code]true[/code]. - To create such a texture file yourself, reimport your image files using the Godot Editor import presets. + [CubemapArray]s are made of an array of [Cubemap]s. Like [Cubemap]s, they are made of multiple textures, the amount of which must be divisible by 6 (one for each face of the cube). The primary benefit of [CubemapArray]s is that they can be accessed in shader code using a single texture reference. In other words, you can pass multiple [Cubemap]s into a shader using a single [CubemapArray]. + Moreover, [Cubemap]s are allocated in adjacent cache regions on the GPU. This makes [CubemapArray]s the most efficient way to store multiple [Cubemap]s. + Internally, Godot uses [CubemapArray]s for many effects, including the [Sky] if you set [member ProjectSettings.rendering/reflections/sky_reflections/texture_array_reflections] to [code]true[/code]. + To create such a texture file yourself, reimport your image files using the import presets of the File System dock. [b]Note:[/b] [CubemapArray] is not supported in the OpenGL 3 rendering backend. </description> <tutorials> diff --git a/doc/classes/Curve.xml b/doc/classes/Curve.xml index 8f5dc4e945..25b06f1063 100644 --- a/doc/classes/Curve.xml +++ b/doc/classes/Curve.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Curve" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - A mathematic curve. + A mathematical curve. </brief_description> <description> - A curve that can be saved and re-used for other objects. By default, it ranges between [code]0[/code] and [code]1[/code] on the Y axis and positions points relative to the [code]0.5[/code] Y position. + This resource describes a mathematical curve by defining a set of points and tangents at each point. By default, it ranges between [code]0[/code] and [code]1[/code] on the Y axis and positions points relative to the [code]0.5[/code] Y position. See also [Gradient] which is designed for color interpolation. See also [Curve2D] and [Curve3D]. </description> <tutorials> diff --git a/doc/classes/CurveTexture.xml b/doc/classes/CurveTexture.xml index 4767c18d5a..8cb2384da3 100644 --- a/doc/classes/CurveTexture.xml +++ b/doc/classes/CurveTexture.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CurveTexture" inherits="Texture2D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - A texture that shows a curve. + A 1D texture where pixel brightness corresponds to points on a curve. </brief_description> <description> - Renders a given [Curve] provided to it. Simplifies the task of drawing curves and/or saving them as image files. + A 1D texture where pixel brightness corresponds to points on a [Curve] resource, either in grayscale or in red. This visual representation simplifies the task of saving curves as image files. If you need to store up to 3 curves within a single texture, use [CurveXYZTexture] instead. See also [GradientTexture1D] and [GradientTexture2D]. </description> <tutorials> diff --git a/doc/classes/CurveXYZTexture.xml b/doc/classes/CurveXYZTexture.xml index e0ab17a35c..8353ed9092 100644 --- a/doc/classes/CurveXYZTexture.xml +++ b/doc/classes/CurveXYZTexture.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="CurveXYZTexture" inherits="Texture2D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - A texture that shows 3 different curves (stored on the red, green and blue color channels). + A 1D texture where the red, green, and blue color channels correspond to points on 3 curves. </brief_description> <description> - Renders 3 given [Curve]s provided to it, on the red, green and blue channels respectively. Compared to using separate [CurveTexture]s, this further simplifies the task of drawing curves and/or saving them as image files. + A 1D texture where the red, green, and blue color channels correspond to points on 3 [Curve] resources. Compared to using separate [CurveTexture]s, this further simplifies the task of saving curves as image files. If you only need to store one curve within a single texture, use [CurveTexture] instead. See also [GradientTexture1D] and [GradientTexture2D]. </description> <tutorials> diff --git a/doc/classes/EditorCommandPalette.xml b/doc/classes/EditorCommandPalette.xml index df8bec8002..e6af82b1c3 100644 --- a/doc/classes/EditorCommandPalette.xml +++ b/doc/classes/EditorCommandPalette.xml @@ -8,13 +8,13 @@ Command key names use slash delimiters to distinguish sections, for example: [code]"example/command1"[/code] then [code]example[/code] will be the section name. [codeblocks] [gdscript] - var command_palette = get_editor_interface().get_command_palette() + var command_palette = EditorInterface.get_command_palette() # external_command is a function that will be called with the command is executed. var command_callable = Callable(self, "external_command").bind(arguments) command_palette.add_command("command", "test/command",command_callable) [/gdscript] [csharp] - EditorCommandPalette commandPalette = GetEditorInterface().GetCommandPalette(); + EditorCommandPalette commandPalette = EditorInterface.Singleton.GetCommandPalette(); // ExternalCommand is a function that will be called with the command is executed. Callable commandCallable = new Callable(this, MethodName.ExternalCommand); commandPalette.AddCommand("command", "test/command", commandCallable) diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml index cabd9c0da6..3ae4b7f812 100644 --- a/doc/classes/EditorInterface.xml +++ b/doc/classes/EditorInterface.xml @@ -5,7 +5,16 @@ </brief_description> <description> [EditorInterface] gives you control over Godot editor's window. It allows customizing the window, saving and (re-)loading scenes, rendering mesh previews, inspecting and editing resources and objects, and provides access to [EditorSettings], [EditorFileSystem], [EditorResourcePreview], [ScriptEditor], the editor viewport, and information about scenes. - [b]Note:[/b] This class shouldn't be instantiated directly. Instead, access the singleton using [method EditorPlugin.get_editor_interface]. + [b]Note:[/b] This class shouldn't be instantiated directly. Instead, access the singleton directly by its name. + [codeblocks] + [gdscript] + var editor_settings = EditorInterface.get_editor_settings() + [/gdscript] + [csharp] + // In C# you can access it via the static Singleton property. + EditorSettings settings = EditorInterface.Singleton.GetEditorSettings(); + [/csharp] + [/codeblocks] </description> <tutorials> </tutorials> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index ffb4df25d3..8d280b8276 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -245,7 +245,7 @@ # You can use a custom icon: return preload("res://addons/my_plugin/my_plugin_icon.svg") # Or use a built-in icon: - return get_editor_interface().get_base_control().get_theme_icon("Node", "EditorIcons") + return EditorInterface.get_base_control().get_theme_icon("Node", "EditorIcons") [/gdscript] [csharp] public override Texture2D _GetPluginIcon() @@ -253,7 +253,7 @@ // You can use a custom icon: return ResourceLoader.Load<Texture2D>("res://addons/my_plugin/my_plugin_icon.svg"); // Or use a built-in icon: - return GetEditorInterface().GetBaseControl().GetThemeIcon("Node", "EditorIcons"); + return EditorInterface.Singleton.GetBaseControl().GetThemeIcon("Node", "EditorIcons"); } [/csharp] [/codeblocks] @@ -340,7 +340,7 @@ func _enter_tree(): plugin_control = preload("my_plugin_control.tscn").instantiate() - get_editor_interface().get_editor_main_screen().add_child(plugin_control) + EditorInterface.get_editor_main_screen().add_child(plugin_control) plugin_control.hide() func _has_main_screen(): @@ -353,7 +353,7 @@ return "My Super Cool Plugin 3000" func _get_plugin_icon(): - return get_editor_interface().get_base_control().get_theme_icon("Node", "EditorIcons") + return EditorInterface.get_base_control().get_theme_icon("Node", "EditorIcons") [/codeblock] </description> </method> @@ -558,10 +558,11 @@ The callback should have 4 arguments: [Object] [code]undo_redo[/code], [Object] [code]modified_object[/code], [String] [code]property[/code] and [Variant] [code]new_value[/code]. They are, respectively, the [UndoRedo] object used by the inspector, the currently modified object, the name of the modified property and the new value the property is about to take. </description> </method> - <method name="get_editor_interface"> + <method name="get_editor_interface" is_deprecated="true"> <return type="EditorInterface" /> <description> - Returns the [EditorInterface] singleton. It provides access to some parts of the editor GUI as well as various inner states and tools. + Returns the [EditorInterface] singleton instance. + [i]Deprecated.[/i] [EditorInterface] is a global singleton and can be accessed directly by its name. </description> </method> <method name="get_export_as_menu"> diff --git a/doc/classes/EditorScript.xml b/doc/classes/EditorScript.xml index 01e6b9a52e..8033c18918 100644 --- a/doc/classes/EditorScript.xml +++ b/doc/classes/EditorScript.xml @@ -48,10 +48,11 @@ [b]Warning:[/b] The implementation of this method is currently disabled. </description> </method> - <method name="get_editor_interface" qualifiers="const"> + <method name="get_editor_interface" qualifiers="const" is_deprecated="true"> <return type="EditorInterface" /> <description> Returns the [EditorInterface] singleton instance. + [i]Deprecated.[/i] [EditorInterface] is a global singleton and can be accessed directly by its name. </description> </method> <method name="get_scene" qualifiers="const"> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 85efe4362e..5ca89dc03e 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -9,7 +9,7 @@ Accessing the settings can be done using the following methods, such as: [codeblocks] [gdscript] - var settings = get_editor_interface().get_editor_settings() + var settings = EditorInterface.get_editor_settings() # `settings.set("some/property", 10)` also works as this class overrides `_set()` internally. settings.set_setting("some/property", 10) # `settings.get("some/property")` also works as this class overrides `_get()` internally. @@ -17,7 +17,7 @@ var list_of_settings = settings.get_property_list() [/gdscript] [csharp] - EditorSettings settings = GetEditorInterface().GetEditorSettings(); + EditorSettings settings = EditorInterface.Singleton.GetEditorSettings(); // `settings.set("some/property", value)` also works as this class overrides `_set()` internally. settings.SetSetting("some/property", Value); // `settings.get("some/property", value)` also works as this class overrides `_get()` internally. diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml index 5c1f6775a1..85fbff7932 100644 --- a/doc/classes/FileAccess.xml +++ b/doc/classes/FileAccess.xml @@ -170,6 +170,14 @@ Returns the next 32 bits from the file as a floating-point number. </description> </method> + <method name="get_hidden_attribute" qualifiers="static"> + <return type="bool" /> + <param index="0" name="file" type="String" /> + <description> + Returns [code]true[/code], if file [code]hidden[/code] attribute is set. + [b]Note:[/b] This method is implemented on iOS, BSD, macOS, and Windows. + </description> + </method> <method name="get_length" qualifiers="const"> <return type="int" /> <description> @@ -228,6 +236,14 @@ Returns the file cursor's position. </description> </method> + <method name="get_read_only_attribute" qualifiers="static"> + <return type="bool" /> + <param index="0" name="file" type="String" /> + <description> + Returns [code]true[/code], if file [code]read only[/code] attribute is set. + [b]Note:[/b] This method is implemented on iOS, BSD, macOS, and Windows. + </description> + </method> <method name="get_real" qualifiers="const"> <return type="float" /> <description> @@ -241,6 +257,14 @@ Returns a SHA-256 [String] representing the file at the given path or an empty [String] on failure. </description> </method> + <method name="get_unix_permissions" qualifiers="static"> + <return type="int" enum="FileAccess.UnixPermissionFlags" is_bitfield="true" /> + <param index="0" name="file" type="String" /> + <description> + Returns file UNIX permissions. + [b]Note:[/b] This method is implemented on iOS, Linux/BSD, and macOS. + </description> + </method> <method name="get_var" qualifiers="const"> <return type="Variant" /> <param index="0" name="allow_objects" type="bool" default="false" /> @@ -312,6 +336,33 @@ [b]Note:[/b] This is an offset, so you should use negative numbers or the cursor will be at the end of the file. </description> </method> + <method name="set_hidden_attribute" qualifiers="static"> + <return type="int" enum="Error" /> + <param index="0" name="file" type="String" /> + <param index="1" name="hidden" type="bool" /> + <description> + Sets file [code]hidden[/code] attribute. + [b]Note:[/b] This method is implemented on iOS, BSD, macOS, and Windows. + </description> + </method> + <method name="set_read_only_attribute" qualifiers="static"> + <return type="int" enum="Error" /> + <param index="0" name="file" type="String" /> + <param index="1" name="ro" type="bool" /> + <description> + Sets file [code]read only[/code] attribute. + [b]Note:[/b] This method is implemented on iOS, BSD, macOS, and Windows. + </description> + </method> + <method name="set_unix_permissions" qualifiers="static"> + <return type="int" enum="Error" /> + <param index="0" name="file" type="String" /> + <param index="1" name="permissions" type="int" enum="FileAccess.UnixPermissionFlags" is_bitfield="true" /> + <description> + Sets file UNIX permissions. + [b]Note:[/b] This method is implemented on iOS, Linux/BSD, and macOS. + </description> + </method> <method name="store_8"> <return type="void" /> <param index="0" name="value" type="int" /> @@ -485,5 +536,41 @@ <constant name="COMPRESSION_BROTLI" value="4" enum="CompressionMode"> Uses the [url=https://github.com/google/brotli]brotli[/url] compression method (only decompression is supported). </constant> + <constant name="UNIX_READ_OWNER" value="256" enum="UnixPermissionFlags" is_bitfield="true"> + Read for owner bit. + </constant> + <constant name="UNIX_WRITE_OWNER" value="128" enum="UnixPermissionFlags" is_bitfield="true"> + Write for owner bit. + </constant> + <constant name="UNIX_EXECUTE_OWNER" value="64" enum="UnixPermissionFlags" is_bitfield="true"> + Execute for owner bit. + </constant> + <constant name="UNIX_READ_GROUP" value="32" enum="UnixPermissionFlags" is_bitfield="true"> + Read for group bit. + </constant> + <constant name="UNIX_WRITE_GROUP" value="16" enum="UnixPermissionFlags" is_bitfield="true"> + Write for group bit. + </constant> + <constant name="UNIX_EXECUTE_GROUP" value="8" enum="UnixPermissionFlags" is_bitfield="true"> + Execute for group bit. + </constant> + <constant name="UNIX_READ_OTHER" value="4" enum="UnixPermissionFlags" is_bitfield="true"> + Read for other bit. + </constant> + <constant name="UNIX_WRITE_OTHER" value="2" enum="UnixPermissionFlags" is_bitfield="true"> + Write for other bit. + </constant> + <constant name="UNIX_EXECUTE_OTHER" value="1" enum="UnixPermissionFlags" is_bitfield="true"> + Execute for other bit. + </constant> + <constant name="UNIX_SET_USER_ID" value="2048" enum="UnixPermissionFlags" is_bitfield="true"> + Set user id on execution bit. + </constant> + <constant name="UNIX_SET_GROUP_ID" value="1024" enum="UnixPermissionFlags" is_bitfield="true"> + Set group id on execution bit. + </constant> + <constant name="UNIX_RESTRICTED_DELETE" value="512" enum="UnixPermissionFlags" is_bitfield="true"> + Restricted deletion (sticky) bit. + </constant> </constants> </class> diff --git a/doc/classes/FogMaterial.xml b/doc/classes/FogMaterial.xml index 13366f1813..a6acaaef00 100644 --- a/doc/classes/FogMaterial.xml +++ b/doc/classes/FogMaterial.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="FogMaterial" inherits="Material" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - [Material] used with a [FogVolume] to draw things with the volumetric fog effect. + A material that controls how volumetric fog is rendered, to be assigned to a [FogVolume]. </brief_description> <description> A [Material] resource that can be used by [FogVolume]s to draw volumetric effects. diff --git a/doc/classes/FogVolume.xml b/doc/classes/FogVolume.xml index 66aeb580c2..538411016e 100644 --- a/doc/classes/FogVolume.xml +++ b/doc/classes/FogVolume.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="FogVolume" inherits="VisualInstance3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - A node used to add local fog with the volumetric fog effect. + A region that contributes to the default volumetric fog from the world environment. </brief_description> <description> [FogVolume]s are used to add localized fog into the global volumetric fog effect. [FogVolume]s can also remove volumetric fog from specific areas if using a [FogMaterial] with a negative [member FogMaterial.density]. diff --git a/doc/classes/GPUParticles2D.xml b/doc/classes/GPUParticles2D.xml index 2928215c3e..d5a4c146e0 100644 --- a/doc/classes/GPUParticles2D.xml +++ b/doc/classes/GPUParticles2D.xml @@ -1,12 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GPUParticles2D" inherits="Node2D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - 2D particle emitter. + A 2D particle emitter. </brief_description> <description> 2D particle node used to create a variety of particle systems and effects. [GPUParticles2D] features an emitter that generates some number of particles at a given rate. Use the [member process_material] property to add a [ParticleProcessMaterial] to configure particle appearance and behavior. Alternatively, you can add a [ShaderMaterial] which will be applied to all particles. - 2D particles can optionally collide with [LightOccluder2D] nodes (note: they don't collide with [PhysicsBody2D] nodes). + 2D particles can optionally collide with [LightOccluder2D], but they don't collide with [PhysicsBody2D] nodes. </description> <tutorials> <link title="Particle systems (2D)">$DOCS_URL/tutorials/2d/particle_systems_2d.html</link> diff --git a/doc/classes/GPUParticles3D.xml b/doc/classes/GPUParticles3D.xml index 4c0b2d12ed..31f1f9e66e 100644 --- a/doc/classes/GPUParticles3D.xml +++ b/doc/classes/GPUParticles3D.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GPUParticles3D" inherits="GeometryInstance3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - 3D particle emitter. + A 3D particle emitter. </brief_description> <description> 3D particle node used to create a variety of particle systems and effects. [GPUParticles3D] features an emitter that generates some number of particles at a given rate. diff --git a/doc/classes/GPUParticlesAttractor3D.xml b/doc/classes/GPUParticlesAttractor3D.xml index b3ed3b4701..0d219e4bc9 100644 --- a/doc/classes/GPUParticlesAttractor3D.xml +++ b/doc/classes/GPUParticlesAttractor3D.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GPUParticlesAttractor3D" inherits="VisualInstance3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Abstract class for 3D particle attractors affecting [GPUParticles3D] nodes. + Abstract base class for 3D particle attractors. </brief_description> <description> Particle attractors can be used to attract particles towards the attractor's origin, or to push them away from the attractor's origin. @@ -25,7 +25,7 @@ [b]Note:[/b] If [member directionality] is greater than [code]0.0[/code], the direction in which particles are pushed can be changed by rotating the [GPUParticlesAttractor3D] node. </member> <member name="strength" type="float" setter="set_strength" getter="get_strength" default="1.0"> - If [member strength] is negative, particles will be pushed in the reverse direction. Particles will be pushed [i]away[/i] from the attractor's origin if [member directionality] is [code]0.0[/code], or towards local +Z if [member directionality] is greater than [code]0.0[/code]. + Adjusts the strength of the attractor. If [member strength] is negative, particles will be pushed in the opposite direction. Particles will be pushed [i]away[/i] from the attractor's origin if [member directionality] is [code]0.0[/code], or towards local +Z if [member directionality] is greater than [code]0.0[/code]. </member> </members> </class> diff --git a/doc/classes/GPUParticlesAttractorBox3D.xml b/doc/classes/GPUParticlesAttractorBox3D.xml index d43efc73bf..410d504428 100644 --- a/doc/classes/GPUParticlesAttractorBox3D.xml +++ b/doc/classes/GPUParticlesAttractorBox3D.xml @@ -1,10 +1,11 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GPUParticlesAttractorBox3D" inherits="GPUParticlesAttractor3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Box-shaped 3D particle attractor affecting [GPUParticles3D] nodes. + A box-shaped attractor that influences particles from [GPUParticles3D] nodes. </brief_description> <description> - Box-shaped 3D particle attractor affecting [GPUParticles3D] nodes. + A box-shaped attractor that influences particles from [GPUParticles3D] nodes. Can be used to attract particles towards its origin, or to push them away from its origin. + Particle attractors work in real-time and can be moved, rotated and scaled during gameplay. Unlike collision shapes, non-uniform scaling of attractors is also supported. [b]Note:[/b] Particle attractors only affect [GPUParticles3D], not [CPUParticles3D]. </description> <tutorials> diff --git a/doc/classes/GPUParticlesAttractorSphere3D.xml b/doc/classes/GPUParticlesAttractorSphere3D.xml index 05c1e60d9a..599d7e4e2a 100644 --- a/doc/classes/GPUParticlesAttractorSphere3D.xml +++ b/doc/classes/GPUParticlesAttractorSphere3D.xml @@ -1,10 +1,11 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GPUParticlesAttractorSphere3D" inherits="GPUParticlesAttractor3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Ellipse-shaped 3D particle attractor affecting [GPUParticles3D] nodes. + A spheroid-shaped attractor that influences particles from [GPUParticles3D] nodes. </brief_description> <description> - Ellipse-shaped 3D particle attractor affecting [GPUParticles3D] nodes. + A spheroid-shaped attractor that influences particles from [GPUParticles3D] nodes. Can be used to attract particles towards its origin, or to push them away from its origin. + Particle attractors work in real-time and can be moved, rotated and scaled during gameplay. Unlike collision shapes, non-uniform scaling of attractors is also supported. [b]Note:[/b] Particle attractors only affect [GPUParticles3D], not [CPUParticles3D]. </description> <tutorials> diff --git a/doc/classes/GPUParticlesAttractorVectorField3D.xml b/doc/classes/GPUParticlesAttractorVectorField3D.xml index ef9a11e57c..7cb5524a1b 100644 --- a/doc/classes/GPUParticlesAttractorVectorField3D.xml +++ b/doc/classes/GPUParticlesAttractorVectorField3D.xml @@ -1,11 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GPUParticlesAttractorVectorField3D" inherits="GPUParticlesAttractor3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Box-shaped 3D particle attractor with strength varying within the box, affecting [GPUParticles3D] nodes. + A box-shaped attractor with varying directions and strengths defined in it that influences particles from [GPUParticles3D] nodes. </brief_description> <description> - Box-shaped 3D particle attractor with strength varying within the box, affecting [GPUParticles3D] nodes. + A box-shaped attractor with varying directions and strengths defined in it that influences particles from [GPUParticles3D] nodes. Unlike [GPUParticlesAttractorBox3D], [GPUParticlesAttractorVectorField3D] uses a [member texture] to affect attraction strength within the box. This can be used to create complex attraction scenarios where particles travel in different directions depending on their location. This can be useful for weather effects such as sandstorms. + Particle attractors work in real-time and can be moved, rotated and scaled during gameplay. Unlike collision shapes, non-uniform scaling of attractors is also supported. [b]Note:[/b] Particle attractors only affect [GPUParticles3D], not [CPUParticles3D]. </description> <tutorials> diff --git a/doc/classes/GPUParticlesCollision3D.xml b/doc/classes/GPUParticlesCollision3D.xml index 9471a7caf2..089747b7ee 100644 --- a/doc/classes/GPUParticlesCollision3D.xml +++ b/doc/classes/GPUParticlesCollision3D.xml @@ -1,11 +1,11 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GPUParticlesCollision3D" inherits="VisualInstance3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Abstract class for 3D particle collision shapes affecting [GPUParticles3D] nodes. + Abstract base class for 3D particle collision shapes affecting [GPUParticles3D] nodes. </brief_description> <description> Particle collision shapes can be used to make particles stop or bounce against them. - Particle collision shapes in real-time and can be moved, rotated and scaled during gameplay. Unlike attractors, non-uniform scaling of collision shapes is [i]not[/i] supported. + Particle collision shapes work in real-time and can be moved, rotated and scaled during gameplay. Unlike attractors, non-uniform scaling of collision shapes is [i]not[/i] supported. Particle collision shapes can be temporarily disabled by hiding them. [b]Note:[/b] [member ParticleProcessMaterial.collision_mode] must be [constant ParticleProcessMaterial.COLLISION_RIGID] or [constant ParticleProcessMaterial.COLLISION_HIDE_ON_CONTACT] on the [GPUParticles3D]'s process material for collision to work. [b]Note:[/b] Particle collision only affects [GPUParticles3D], not [CPUParticles3D]. diff --git a/doc/classes/GPUParticlesCollisionBox3D.xml b/doc/classes/GPUParticlesCollisionBox3D.xml index 4bb947a82d..fc225e460f 100644 --- a/doc/classes/GPUParticlesCollisionBox3D.xml +++ b/doc/classes/GPUParticlesCollisionBox3D.xml @@ -1,10 +1,11 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GPUParticlesCollisionBox3D" inherits="GPUParticlesCollision3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Box-shaped 3D particle collision shape affecting [GPUParticles3D] nodes. + A box-shaped 3D particle collision shape affecting [GPUParticles3D] nodes. </brief_description> <description> - Box-shaped 3D particle collision shape affecting [GPUParticles3D] nodes. + A box-shaped 3D particle collision shape affecting [GPUParticles3D] nodes. + Particle collision shapes work in real-time and can be moved, rotated and scaled during gameplay. Unlike attractors, non-uniform scaling of collision shapes is [i]not[/i] supported. [b]Note:[/b] [member ParticleProcessMaterial.collision_mode] must be [constant ParticleProcessMaterial.COLLISION_RIGID] or [constant ParticleProcessMaterial.COLLISION_HIDE_ON_CONTACT] on the [GPUParticles3D]'s process material for collision to work. [b]Note:[/b] Particle collision only affects [GPUParticles3D], not [CPUParticles3D]. </description> diff --git a/doc/classes/GPUParticlesCollisionHeightField3D.xml b/doc/classes/GPUParticlesCollisionHeightField3D.xml index 58219aa0b3..0b9a94af63 100644 --- a/doc/classes/GPUParticlesCollisionHeightField3D.xml +++ b/doc/classes/GPUParticlesCollisionHeightField3D.xml @@ -1,12 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GPUParticlesCollisionHeightField3D" inherits="GPUParticlesCollision3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Real-time heightmap-shaped 3D particle attractor affecting [GPUParticles3D] nodes. + A real-time heightmap-shaped 3D particle collision shape affecting [GPUParticles3D] nodes. </brief_description> <description> - Real-time heightmap-shaped 3D particle attractor affecting [GPUParticles3D] nodes. + A real-time heightmap-shaped 3D particle collision shape affecting [GPUParticles3D] nodes. Heightmap shapes allow for efficiently representing collisions for convex and concave objects with a single "floor" (such as terrain). This is less flexible than [GPUParticlesCollisionSDF3D], but it doesn't require a baking step. - [GPUParticlesCollisionHeightField3D] can also be regenerated in real-time when it is moved, when the camera moves, or even continuously. This makes [GPUParticlesCollisionHeightField3D] a good choice for weather effects such as rain and snow and games with highly dynamic geometry. However, since heightmaps cannot represent overhangs, [GPUParticlesCollisionHeightField3D] is not suited for indoor particle collision. + [GPUParticlesCollisionHeightField3D] can also be regenerated in real-time when it is moved, when the camera moves, or even continuously. This makes [GPUParticlesCollisionHeightField3D] a good choice for weather effects such as rain and snow and games with highly dynamic geometry. However, this class is limited since heightmaps cannot represent overhangs (e.g. indoors or caves). [b]Note:[/b] [member ParticleProcessMaterial.collision_mode] must be [code]true[/code] on the [GPUParticles3D]'s process material for collision to work. [b]Note:[/b] Particle collision only affects [GPUParticles3D], not [CPUParticles3D]. </description> diff --git a/doc/classes/GPUParticlesCollisionSDF3D.xml b/doc/classes/GPUParticlesCollisionSDF3D.xml index b61be49619..11978e0737 100644 --- a/doc/classes/GPUParticlesCollisionSDF3D.xml +++ b/doc/classes/GPUParticlesCollisionSDF3D.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GPUParticlesCollisionSDF3D" inherits="GPUParticlesCollision3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Baked signed distance field 3D particle attractor affecting [GPUParticles3D] nodes. + A baked signed distance field 3D particle collision shape affecting [GPUParticles3D] nodes. </brief_description> <description> - Baked signed distance field 3D particle attractor affecting [GPUParticles3D] nodes. + A baked signed distance field 3D particle collision shape affecting [GPUParticles3D] nodes. Signed distance fields (SDF) allow for efficiently representing approximate collision shapes for convex and concave objects of any shape. This is more flexible than [GPUParticlesCollisionHeightField3D], but it requires a baking step. [b]Baking:[/b] The signed distance field texture can be baked by selecting the [GPUParticlesCollisionSDF3D] node in the editor, then clicking [b]Bake SDF[/b] at the top of the 3D viewport. Any [i]visible[/i] [MeshInstance3D]s within the [member size] will be taken into account for baking, regardless of their [member GeometryInstance3D.gi_mode]. [b]Note:[/b] Baking a [GPUParticlesCollisionSDF3D]'s [member texture] is only possible within the editor, as there is no bake method exposed for use in exported projects. However, it's still possible to load pre-baked [Texture3D]s into its [member texture] property in an exported project. diff --git a/doc/classes/GPUParticlesCollisionSphere3D.xml b/doc/classes/GPUParticlesCollisionSphere3D.xml index cade0eaeef..fe2fe3d414 100644 --- a/doc/classes/GPUParticlesCollisionSphere3D.xml +++ b/doc/classes/GPUParticlesCollisionSphere3D.xml @@ -1,10 +1,11 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GPUParticlesCollisionSphere3D" inherits="GPUParticlesCollision3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Sphere-shaped 3D particle collision shape affecting [GPUParticles3D] nodes. + A sphere-shaped 3D particle collision shape affecting [GPUParticles3D] nodes. </brief_description> <description> - Sphere-shaped 3D particle collision shape affecting [GPUParticles3D] nodes. + A sphere-shaped 3D particle collision shape affecting [GPUParticles3D] nodes. + Particle collision shapes work in real-time and can be moved, rotated and scaled during gameplay. Unlike attractors, non-uniform scaling of collision shapes is [i]not[/i] supported. [b]Note:[/b] [member ParticleProcessMaterial.collision_mode] must be [constant ParticleProcessMaterial.COLLISION_RIGID] or [constant ParticleProcessMaterial.COLLISION_HIDE_ON_CONTACT] on the [GPUParticles3D]'s process material for collision to work. [b]Note:[/b] Particle collision only affects [GPUParticles3D], not [CPUParticles3D]. </description> diff --git a/doc/classes/Geometry3D.xml b/doc/classes/Geometry3D.xml index 85b8965faf..a85d17d925 100644 --- a/doc/classes/Geometry3D.xml +++ b/doc/classes/Geometry3D.xml @@ -45,6 +45,13 @@ Clips the polygon defined by the points in [param points] against the [param plane] and returns the points of the clipped polygon. </description> </method> + <method name="compute_convex_mesh_points"> + <return type="PackedVector3Array" /> + <param index="0" name="planes" type="Plane[]" /> + <description> + Calculates and returns all the vertex points of a convex shape defined by an array of [param planes]. + </description> + </method> <method name="get_closest_point_to_segment"> <return type="Vector3" /> <param index="0" name="point" type="Vector3" /> diff --git a/doc/classes/Gradient.xml b/doc/classes/Gradient.xml index f2b894d612..c7fa1f40e0 100644 --- a/doc/classes/Gradient.xml +++ b/doc/classes/Gradient.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Gradient" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - A color interpolator resource which can be used to generate colors between user-defined color points. + A color transition. </brief_description> <description> - Given a set of colors, this resource will interpolate them in order. This means that if you have color 1, color 2 and color 3, the gradient will interpolate from color 1 to color 2 and from color 2 to color 3. The gradient will initially have 2 colors (black and white), one (black) at gradient lower offset 0 and the other (white) at the gradient higher offset 1. + This resource describes a color transition by defining a set of colored points and how to interpolate between them. See also [Curve] which supports more complex easing methods, but does not support colors. </description> <tutorials> @@ -15,7 +15,7 @@ <param index="0" name="offset" type="float" /> <param index="1" name="color" type="Color" /> <description> - Adds the specified color to the end of the gradient, with the specified offset. + Adds the specified color to the gradient, with the specified offset. </description> </method> <method name="get_color"> @@ -42,7 +42,7 @@ <return type="void" /> <param index="0" name="point" type="int" /> <description> - Removes the color at the index [param point]. + Removes the color at index [param point]. </description> </method> <method name="reverse"> diff --git a/doc/classes/GradientTexture1D.xml b/doc/classes/GradientTexture1D.xml index 7207de57fd..4e6b76c0a6 100644 --- a/doc/classes/GradientTexture1D.xml +++ b/doc/classes/GradientTexture1D.xml @@ -1,16 +1,16 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GradientTexture1D" inherits="Texture2D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Gradient-filled texture. + A 1D texture that uses colors obtained from a [Gradient]. </brief_description> <description> - GradientTexture1D uses a [Gradient] to fill the texture data. The gradient will be filled from left to right using colors obtained from the gradient. This means the texture does not necessarily represent an exact copy of the gradient, but instead an interpolation of samples obtained from the gradient at fixed steps (see [member width]). See also [GradientTexture2D], [CurveTexture] and [CurveXYZTexture]. + A 1D texture that obtains colors from a [Gradient] to fill the texture data. The texture is filled by sampling the gradient for each pixel. Therefore, the texture does not necessarily represent an exact copy of the gradient, as it may miss some colors if there are not enough pixels. See also [GradientTexture2D], [CurveTexture] and [CurveXYZTexture]. </description> <tutorials> </tutorials> <members> <member name="gradient" type="Gradient" setter="set_gradient" getter="get_gradient"> - The [Gradient] that will be used to fill the texture. + The [Gradient] used to fill the texture. </member> <member name="resource_local_to_scene" type="bool" setter="set_local_to_scene" getter="is_local_to_scene" overrides="Resource" default="false" /> <member name="use_hdr" type="bool" setter="set_use_hdr" getter="is_using_hdr" default="false"> diff --git a/doc/classes/GradientTexture2D.xml b/doc/classes/GradientTexture2D.xml index 63e511173a..f6588ef791 100644 --- a/doc/classes/GradientTexture2D.xml +++ b/doc/classes/GradientTexture2D.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GradientTexture2D" inherits="Texture2D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Gradient-filled 2D texture. + A 2D texture that creates a pattern with colors obtained from a [Gradient]. </brief_description> <description> - The texture uses a [Gradient] to fill the texture data in 2D space. The gradient is filled according to the specified [member fill] and [member repeat] types using colors obtained from the gradient. The texture does not necessarily represent an exact copy of the gradient, but instead an interpolation of samples obtained from the gradient at fixed steps (see [member width] and [member height]). See also [GradientTexture1D], [CurveTexture] and [CurveXYZTexture]. + A 2D texture that obtains colors from a [Gradient] to fill the texture data. This texture is able to transform a color transition into different patterns such as a linear or a radial gradient. The gradient is sampled individually for each pixel so it does not necessarily represent an exact copy of the gradient(see [member width] and [member height]). See also [GradientTexture1D], [CurveTexture] and [CurveXYZTexture]. </description> <tutorials> </tutorials> diff --git a/doc/classes/Material.xml b/doc/classes/Material.xml index 505a1465bb..1118461445 100644 --- a/doc/classes/Material.xml +++ b/doc/classes/Material.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Material" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Abstract base [Resource] for coloring and shading geometry. + Abstract base class for applying visual properties to an object, such as color and roughness. </brief_description> <description> - Material is a base [Resource] used for coloring and shading geometry. All materials inherit from it and almost all [VisualInstance3D] derived nodes carry a Material. A few flags and parameters are shared between all material types and are configured here. + [Material] is a base resource used for coloring and shading geometry. All materials inherit from it and almost all [VisualInstance3D] derived nodes carry a [Material]. A few flags and parameters are shared between all material types and are configured here. </description> <tutorials> <link title="3D Material Testers Demo">https://godotengine.org/asset-library/asset/123</link> diff --git a/doc/classes/MenuButton.xml b/doc/classes/MenuButton.xml index 8af233427c..e3a707ba72 100644 --- a/doc/classes/MenuButton.xml +++ b/doc/classes/MenuButton.xml @@ -69,7 +69,7 @@ <theme_item name="font_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> Text [Color] used when the [MenuButton] is being pressed. </theme_item> - <theme_item name="h_separation" data_type="constant" type="int" default="3"> + <theme_item name="h_separation" data_type="constant" type="int" default="4"> The horizontal space between [MenuButton]'s icon and text. Negative values will be treated as [code]0[/code] when used. </theme_item> <theme_item name="outline_size" data_type="constant" type="int" default="0"> diff --git a/doc/classes/NavigationAgent2D.xml b/doc/classes/NavigationAgent2D.xml index c1570f3149..71f8c50667 100644 --- a/doc/classes/NavigationAgent2D.xml +++ b/doc/classes/NavigationAgent2D.xml @@ -180,7 +180,7 @@ The distance to search for other agents. </member> <member name="path_desired_distance" type="float" setter="set_path_desired_distance" getter="get_path_desired_distance" default="20.0"> - The distance threshold before a path point is considered to be reached. This allows agents to not have to hit a path point on the path exactly, but only to reach its general area. If this value is set too high, the NavigationAgent will skip points on the path, which can lead too leaving the navigation mesh. If this value is set too low, the NavigationAgent will be stuck in a repath loop because it will constantly overshoot or undershoot the distance to the next point on each physics frame update. + The distance threshold before a path point is considered to be reached. This allows agents to not have to hit a path point on the path exactly, but only to reach its general area. If this value is set too high, the NavigationAgent will skip points on the path, which can lead to leaving the navigation mesh. If this value is set too low, the NavigationAgent will be stuck in a repath loop because it will constantly overshoot or undershoot the distance to the next point on each physics frame update. </member> <member name="path_max_distance" type="float" setter="set_path_max_distance" getter="get_path_max_distance" default="100.0"> The maximum distance the agent is allowed away from the ideal path to the final position. This can happen due to trying to avoid collisions. When the maximum distance is exceeded, it recalculates the ideal path. diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 49ab3918bb..7510d6bb64 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -1022,12 +1022,12 @@ Notification received right after the scene with the node is saved in the editor. This notification is only sent in the Godot editor and will not occur in exported projects. </constant> <constant name="NOTIFICATION_WM_MOUSE_ENTER" value="1002"> - Notification received from the OS when the mouse enters the game window. - Implemented on desktop and web platforms. + Notification received when the mouse enters the window. + Implemented for embedded windows and on desktop and web platforms. </constant> <constant name="NOTIFICATION_WM_MOUSE_EXIT" value="1003"> - Notification received from the OS when the mouse leaves the game window. - Implemented on desktop and web platforms. + Notification received when the mouse leaves the window. + Implemented for embedded windows and on desktop and web platforms. </constant> <constant name="NOTIFICATION_WM_WINDOW_FOCUS_IN" value="1004"> Notification received when the node's parent [Window] is focused. This may be a change of focus between two windows of the same engine instance, or from the OS desktop or a third-party application to a window of the game (in which case [constant NOTIFICATION_APPLICATION_FOCUS_IN] is also emitted). diff --git a/doc/classes/NodePath.xml b/doc/classes/NodePath.xml index 77a5cfa6c6..a56e80ec1f 100644 --- a/doc/classes/NodePath.xml +++ b/doc/classes/NodePath.xml @@ -101,12 +101,12 @@ Returns all subnames concatenated with a colon character ([code]:[/code]) as separator, i.e. the right side of the first colon in a node path. [codeblocks] [gdscript] - var nodepath = NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path") - print(nodepath.get_concatenated_subnames()) # texture:load_path + var node_path = NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path") + print(node_path.get_concatenated_subnames()) # texture:load_path [/gdscript] [csharp] - var nodepath = new NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path"); - GD.Print(nodepath.GetConcatenatedSubnames()); // texture:load_path + var nodePath = new NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path"); + GD.Print(nodePath.GetConcatenatedSubnames()); // texture:load_path [/csharp] [/codeblocks] </description> diff --git a/doc/classes/ORMMaterial3D.xml b/doc/classes/ORMMaterial3D.xml index d134107618..72620573da 100644 --- a/doc/classes/ORMMaterial3D.xml +++ b/doc/classes/ORMMaterial3D.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="ORMMaterial3D" inherits="BaseMaterial3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Physically based rendering (PBR) material that can be applied to 3D objects, can use an ORM texture. + A PBR (Physically Based Rendering) material to be used on 3D objects. Uses an ORM texture. </brief_description> <description> ORMMaterial3D's properties are inherited from [BaseMaterial3D]. Unlike [StandardMaterial3D], ORMMaterial3D uses a single texture for ambient occlusion, roughness and metallic maps, known as an ORM texture. diff --git a/doc/classes/OptionButton.xml b/doc/classes/OptionButton.xml index 659f08d49a..7737754fc6 100644 --- a/doc/classes/OptionButton.xml +++ b/doc/classes/OptionButton.xml @@ -262,7 +262,7 @@ <theme_item name="arrow_margin" data_type="constant" type="int" default="4"> The horizontal space between the arrow icon and the right edge of the button. </theme_item> - <theme_item name="h_separation" data_type="constant" type="int" default="2"> + <theme_item name="h_separation" data_type="constant" type="int" default="4"> The horizontal space between [OptionButton]'s icon and text. Negative values will be treated as [code]0[/code] when used. </theme_item> <theme_item name="modulate_arrow" data_type="constant" type="int" default="0"> diff --git a/doc/classes/PanoramaSkyMaterial.xml b/doc/classes/PanoramaSkyMaterial.xml index ab32ca48dd..0d041b9b90 100644 --- a/doc/classes/PanoramaSkyMaterial.xml +++ b/doc/classes/PanoramaSkyMaterial.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="PanoramaSkyMaterial" inherits="Material" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - A [Material] used with [Sky] to draw a background texture. + A material that provides a special texture to a [Sky], usually an HDR panorama. </brief_description> <description> - A resource referenced in a [Sky] that is used to draw a background. The Panorama sky material functions similar to skyboxes in other engines, except it uses an equirectangular sky map instead of a cubemap. + A resource referenced in a [Sky] that is used to draw a background. [PanoramaSkyMaterial] functions similar to skyboxes in other engines, except it uses an equirectangular sky map instead of a [Cubemap]. Using an HDR panorama is strongly recommended for accurate, high-quality reflections. Godot supports the Radiance HDR ([code].hdr[/code]) and OpenEXR ([code].exr[/code]) image formats for this purpose. You can use [url=https://danilw.github.io/GLSL-howto/cubemap_to_panorama_js/cubemap_to_panorama.html]this tool[/url] to convert a cubemap to an equirectangular sky map. </description> diff --git a/doc/classes/ParticleProcessMaterial.xml b/doc/classes/ParticleProcessMaterial.xml index 69d3351733..355fec3713 100644 --- a/doc/classes/ParticleProcessMaterial.xml +++ b/doc/classes/ParticleProcessMaterial.xml @@ -1,12 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="ParticleProcessMaterial" inherits="Material" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Particle properties for [GPUParticles3D] and [GPUParticles2D] nodes. + Holds a particle configuration for [GPUParticles2D] or [GPUParticles3D] nodes. </brief_description> <description> - ParticleProcessMaterial defines particle properties and behavior. It is used in the [code]process_material[/code] of [GPUParticles3D] and [GPUParticles2D] emitter nodes. - Some of this material's properties are applied to each particle when emitted, while others can have a [CurveTexture] applied to vary values over the lifetime of the particle. - Particle animation is available only in [GPUParticles2D]. To use it, attach a [CanvasItemMaterial], with [member CanvasItemMaterial.particles_animation] enabled, to the particles node. + [ParticleProcessMaterial] defines particle properties and behavior. It is used in the [code]process_material[/code] of the [GPUParticles2D] and [GPUParticles3D] nodes. Some of this material's properties are applied to each particle when emitted, while others can have a [CurveTexture] or a [GradientTexture1D] applied to vary numerical or color values over the lifetime of the particle. </description> <tutorials> </tutorials> diff --git a/doc/classes/PhysicalSkyMaterial.xml b/doc/classes/PhysicalSkyMaterial.xml index e6a9980e19..13f9d93e66 100644 --- a/doc/classes/PhysicalSkyMaterial.xml +++ b/doc/classes/PhysicalSkyMaterial.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="PhysicalSkyMaterial" inherits="Material" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - [Sky] [Material] used for a physically based sky. + A material that defines a sky for a [Sky] resource by a set of physical properties. </brief_description> <description> The [PhysicalSkyMaterial] uses the Preetham analytic daylight model to draw a sky based on physical properties. This results in a substantially more realistic sky than the [ProceduralSkyMaterial], but it is slightly slower and less flexible. diff --git a/doc/classes/PlaceholderCubemap.xml b/doc/classes/PlaceholderCubemap.xml index b760bf359b..9cd0c26926 100644 --- a/doc/classes/PlaceholderCubemap.xml +++ b/doc/classes/PlaceholderCubemap.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="PlaceholderCubemap" inherits="PlaceholderTextureLayered" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Placeholder class for a cubemap texture. + A [Cubemap] without image data. </brief_description> <description> - This class is used when loading a project that uses a [Cubemap] subclass in 2 conditions: - - When running the project exported in dedicated server mode, only the texture's dimensions are kept (as they may be relied upon for gameplay purposes or positioning of other elements). This allows reducing the exported PCK's size significantly. - - When this subclass is missing due to using a different engine version or build (e.g. modules disabled). - [b]Note:[/b] This is not intended to be used as an actual texture for rendering. It is not guaranteed to work like one in shaders or materials (for example when calculating UV). + This class replaces a [Cubemap] or a [Cubemap]-derived class in 2 conditions: + - In dedicated server mode, where the image data shouldn't affect game logic. This allows reducing the exported PCK's size significantly. + - When the [Cubemap]-derived class is missing, for example when using a different engine version. + [b]Note:[/b] This class is not intended for rendering or for use in shaders. Operations like calculating UV are not guaranteed to work. </description> <tutorials> </tutorials> diff --git a/doc/classes/PlaceholderCubemapArray.xml b/doc/classes/PlaceholderCubemapArray.xml index 1074824c0f..9ffbc316a8 100644 --- a/doc/classes/PlaceholderCubemapArray.xml +++ b/doc/classes/PlaceholderCubemapArray.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="PlaceholderCubemapArray" inherits="PlaceholderTextureLayered" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Placeholder class for a cubemap texture array. + A [CubemapArray] without image data. </brief_description> <description> - This class is used when loading a project that uses a [CubemapArray] subclass in 2 conditions: - - When running the project exported in dedicated server mode, only the texture's dimensions are kept (as they may be relied upon for gameplay purposes or positioning of other elements). This allows reducing the exported PCK's size significantly. - - When this subclass is missing due to using a different engine version or build (e.g. modules disabled). - [b]Note:[/b] This is not intended to be used as an actual texture for rendering. It is not guaranteed to work like one in shaders or materials (for example when calculating UV). + This class replaces a [CubemapArray] or a [CubemapArray]-derived class in 2 conditions: + - In dedicated server mode, where the image data shouldn't affect game logic. This allows reducing the exported PCK's size significantly. + - When the [CubemapArray]-derived class is missing, for example when using a different engine version. + [b]Note:[/b] This class is not intended for rendering or for use in shaders. Operations like calculating UV are not guaranteed to work. </description> <tutorials> </tutorials> diff --git a/doc/classes/ProceduralSkyMaterial.xml b/doc/classes/ProceduralSkyMaterial.xml index e3a1177cfa..54bffd009f 100644 --- a/doc/classes/ProceduralSkyMaterial.xml +++ b/doc/classes/ProceduralSkyMaterial.xml @@ -1,12 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="ProceduralSkyMaterial" inherits="Material" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - A [Material] used with [Sky] to generate a background based on user input parameters. + A material that defines a simple sky for a [Sky] resource. </brief_description> <description> - ProceduralSkyMaterial provides a way to create an effective background quickly by defining procedural parameters for the sun, the sky and the ground. The sky and ground are very similar, they are defined by a color at the horizon, another color, and finally an easing curve to interpolate between these two colors. Similarly, the sun is described by a position in the sky, a color, and an easing curve. However, the sun also defines a minimum and maximum angle, these two values define at what distance the easing curve begins and ends from the sun, and thus end up defining the size of the sun in the sky. - The [ProceduralSkyMaterial] uses a lightweight shader to draw the sky and is thus suited for real time updates. When you do not need a quick sky that is not realistic, this is a good option. If you need a more realistic option, try using [PhysicalSkyMaterial] instead. - The [ProceduralSkyMaterial] supports up to 4 suns. Each sun takes its color, energy, and direction from the corresponding [DirectionalLight3D] in the scene. + [ProceduralSkyMaterial] provides a way to create an effective background quickly by defining procedural parameters for the sun, the sky and the ground. The sky and ground are defined by a main color, a color at the horizon, and an easing curve to interpolate between them. Suns are described by a position in the sky, a color, and a max angle from the sun at which the easing curve ends. The max angle therefore defines the size of the sun in the sky. + [ProceduralSkyMaterial] supports up to 4 suns, using the color, and energy, direction, and angular distance of the first four [DirectionalLight3D] nodes in the scene. This means that the suns are defined individually by the properties of their corresponding [DirectionalLight3D]s and globally by [member sun_angle_max] and [member sun_curve]. + [ProceduralSkyMaterial] uses a lightweight shader to draw the sky and is therefore suited for real time updates. This makes it a great option for a sky that is simple and computationally cheap, but unrealistic. If you need a more realistic procedural option, use [PhysicalSkyMaterial]. </description> <tutorials> </tutorials> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 40a475851d..1be69052e4 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -298,7 +298,7 @@ [b]Note:[/b] Changing this value can help on platforms or with third-party tools where hidden directory patterns are disallowed. Only modify this setting if you know that your environment requires it, as changing the default can impact compatibility with some external tools or plugins which expect the default [code].godot[/code] folder. </member> <member name="application/config/version" type="String" setter="" getter="" default=""""> - The project's human-readable version identifier. This should always be set to a non-empty string, as some exporters rely on this value being defined. + The project's human-readable version identifier. This is used by exporters if the version identifier isn't overridden there. If [member application/config/version] is an empty string and the version identifier isn't overridden in an exporter, the exporter will use [code]1.0.0[/code] as a version identifier. </member> <member name="application/config/windows_native_icon" type="String" setter="" getter="" default=""""> Icon set in [code].ico[/code] format used on Windows to set the game's icon. This is done automatically on start by calling [method DisplayServer.set_native_icon]. @@ -813,6 +813,10 @@ [b]"viewport"[/b]: The size of the root [Viewport] is set precisely to the base size specified in the Project Settings' Display section. The scene is rendered to this viewport first. Finally, this viewport is scaled to fit the screen (taking [member display/window/stretch/aspect] into account). Recommended for games that use a pixel art esthetic. </member> <member name="display/window/stretch/scale" type="float" setter="" getter="" default="1.0"> + The scale factor multiplier to use for 2D elements. This multiplies the final scale factor determined by [member display/window/stretch/mode]. If using the [b]Disabled[/b] stretch mode, this scale factor is applied as-is. This can be adjusted to make the UI easier to read on certain displays. + </member> + <member name="display/window/stretch/scale_mode" type="String" setter="" getter="" default=""fractional""> + The policy to use to determine the final scale factor for 2D elements. This affects how [member display/window/stretch/scale] is applied, in addition to the automatic scale factor determined by [member display/window/stretch/mode]. </member> <member name="display/window/subwindows/embed_subwindows" type="bool" setter="" getter="" default="true"> If [code]true[/code] subwindows are embedded in the main window. @@ -2710,4 +2714,11 @@ If [code]true[/code], Godot will compile shaders required for XR. </member> </members> + <signals> + <signal name="settings_changed"> + <description> + Emitted when any setting is changed, up to once per process frame. + </description> + </signal> + </signals> </class> diff --git a/doc/classes/ShaderGlobalsOverride.xml b/doc/classes/ShaderGlobalsOverride.xml index afd6ee527e..c5fe2a3e8a 100644 --- a/doc/classes/ShaderGlobalsOverride.xml +++ b/doc/classes/ShaderGlobalsOverride.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="ShaderGlobalsOverride" inherits="Node" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Overrides global shader parameters' values in a specific scene. + A node used to override global shader parameters' values in a scene. </brief_description> <description> Similar to how a [WorldEnvironment] node can be used to override the environment while a specific scene is loaded, [ShaderGlobalsOverride] can be used to override global shader parameters temporarily. Once the node is removed, the project-wide values for the global shader parameters are restored. See the [RenderingServer] [code]global_shader_parameter_*[/code] methods for more information. @@ -9,5 +9,6 @@ [b]Note:[/b] All [ShaderGlobalsOverride] nodes are made part of a [code]"shader_overrides_group"[/code] group when they are added to the scene tree. The currently active [ShaderGlobalsOverride] node also has a [code]"shader_overrides_group_active"[/code] group added to it. You can use this to check which [ShaderGlobalsOverride] node is currently active. </description> <tutorials> + <link title="Shading language">$DOCS_URL/tutorials/shaders/shader_reference/shading_language.html</link> </tutorials> </class> diff --git a/doc/classes/ShaderMaterial.xml b/doc/classes/ShaderMaterial.xml index e2d0696a4b..d0752b4b25 100644 --- a/doc/classes/ShaderMaterial.xml +++ b/doc/classes/ShaderMaterial.xml @@ -1,11 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="ShaderMaterial" inherits="Material" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - A material that uses a custom [Shader] program. + A material defined by a custom [Shader] program and the values of its shader parameters. </brief_description> <description> - A material that uses a custom [Shader] program to render either items to screen or process particles. You can create multiple materials for the same shader but configure different values for the uniforms defined in the shader. - [b]Note:[/b] For performance reasons the [signal Resource.changed] signal is only emitted when the [member Resource.resource_name] is changed. Only in editor, is also emitted for [member shader] changes. + A material that uses a custom [Shader] program to render visual items (canvas items, meshes, skies, fog), or to process particles. Compared to other materials, [ShaderMaterial] gives deeper control over the generated shader code. For more information, see the shaders documentation index below. + Multiple [ShaderMaterial]s can use the same shader and configure different values for the shader uniforms. + [b]Note:[/b] For performance reasons, the [signal Resource.changed] signal is only emitted when the [member Resource.resource_name] changes. Only in editor, it is also emitted for [member shader] changes. </description> <tutorials> <link title="Shaders documentation index">$DOCS_URL/tutorials/shaders/index.html</link> diff --git a/doc/classes/Sky.xml b/doc/classes/Sky.xml index d52fd6ce40..d92319b390 100644 --- a/doc/classes/Sky.xml +++ b/doc/classes/Sky.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Sky" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Background that uses a [Material] to draw a sky. + Defines a 3D environment's background by using a [Material]. </brief_description> <description> - The [Sky] class uses a [Material] to draw the background and update the reflection/radiance cubemaps. + The [Sky] class uses a [Material] to render a 3D environment's background and the light it emits by updating the reflection/radiance cubemaps. </description> <tutorials> </tutorials> diff --git a/doc/classes/StandardMaterial3D.xml b/doc/classes/StandardMaterial3D.xml index 8814169d1d..55247678cb 100644 --- a/doc/classes/StandardMaterial3D.xml +++ b/doc/classes/StandardMaterial3D.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="StandardMaterial3D" inherits="BaseMaterial3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Physically based rendering (PBR) material that can be applied to 3D objects. + A PBR (Physically Based Rendering) material to be used on 3D objects. </brief_description> <description> [StandardMaterial3D]'s properties are inherited from [BaseMaterial3D]. [StandardMaterial3D] uses separate textures for ambient occlusion, roughness and metallic maps. To use a single ORM map for all 3 textures, use an [ORMMaterial3D] instead. diff --git a/doc/classes/String.xml b/doc/classes/String.xml index 913f2f2654..9c31576b72 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -720,6 +720,12 @@ Replaces all [b]case-insensitive[/b] occurrences of [param what] inside the string with the given [param forwhat]. </description> </method> + <method name="reverse" qualifiers="const"> + <return type="String" /> + <description> + Returns the copy of this string in reverse order. + </description> + </method> <method name="rfind" qualifiers="const"> <return type="int" /> <param index="0" name="what" type="String" /> diff --git a/doc/classes/StringName.xml b/doc/classes/StringName.xml index 0814a30a8d..1c5032be9b 100644 --- a/doc/classes/StringName.xml +++ b/doc/classes/StringName.xml @@ -627,6 +627,12 @@ Replaces all [b]case-insensitive[/b] occurrences of [param what] inside the string with the given [param forwhat]. </description> </method> + <method name="reverse" qualifiers="const"> + <return type="String" /> + <description> + Returns the copy of this string in reverse order. + </description> + </method> <method name="rfind" qualifiers="const"> <return type="int" /> <param index="0" name="what" type="String" /> diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml index a1932ba5b6..004e93ac04 100644 --- a/doc/classes/Tree.xml +++ b/doc/classes/Tree.xml @@ -606,6 +606,9 @@ <theme_item name="font_size" data_type="font_size" type="int"> Font size of the item's text. </theme_item> + <theme_item name="title_button_font_size" data_type="font_size" type="int"> + Font size of the title button's text. + </theme_item> <theme_item name="arrow" data_type="icon" type="Texture2D"> The arrow icon used when a foldable item is not collapsed. </theme_item> diff --git a/doc/classes/VisualShader.xml b/doc/classes/VisualShader.xml index bf48d80c66..f424a27a8f 100644 --- a/doc/classes/VisualShader.xml +++ b/doc/classes/VisualShader.xml @@ -4,10 +4,10 @@ A custom shader program with a visual editor. </brief_description> <description> - This class allows you to define a custom shader program that can be used for various materials to render objects. - The visual shader editor creates the shader. + This class provides a graph-like visual editor for creating a [Shader]. Although [VisualShader]s do not require coding, they share the same logic with script shaders. They use [VisualShaderNode]s that can be connected to each other to control the flow of the shader. The visual shader graph is converted to a script shader behind the scenes. </description> <tutorials> + <link title="Using VisualShaders">$DOCS_URL/tutorials/shaders/visual_shaders.html</link> </tutorials> <methods> <method name="add_node"> diff --git a/doc/classes/VisualShaderNode.xml b/doc/classes/VisualShaderNode.xml index 5d147bd542..baf9eded56 100644 --- a/doc/classes/VisualShaderNode.xml +++ b/doc/classes/VisualShaderNode.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="VisualShaderNode" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Base class for nodes in a visual shader graph. + Base class for [VisualShader] nodes. Not related to scene nodes. </brief_description> <description> - Visual shader graphs consist of various nodes. Each node in the graph is a separate object and they are represented as a rectangular boxes with title and a set of properties. Each node has also connection ports that allow to connect it to another nodes and control the flow of the shader. + Visual shader graphs consist of various nodes. Each node in the graph is a separate object and they are represented as a rectangular boxes with title and a set of properties. Each node also has connection ports that allow to connect it to another nodes and control the flow of the shader. </description> <tutorials> - <link title="VisualShaders">$DOCS_URL/tutorials/shaders/visual_shaders.html</link> + <link title="Using VisualShaders">$DOCS_URL/tutorials/shaders/visual_shaders.html</link> </tutorials> <methods> <method name="clear_default_input_values"> diff --git a/doc/classes/VisualShaderNodeIf.xml b/doc/classes/VisualShaderNodeIf.xml index 5fc9a0cdf4..82799bb5fd 100644 --- a/doc/classes/VisualShaderNodeIf.xml +++ b/doc/classes/VisualShaderNodeIf.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="VisualShaderNodeIf" inherits="VisualShaderNode" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Compares two floating-point numbers in order to return a required vector within the visual shader graph. + Outputs a 3D vector based on the result of a floating point comparison within the visual shader graph. </brief_description> <description> - First two ports are scalar floating-point numbers to compare, third is tolerance comparison amount and last three ports represents a vectors returned if [code]a == b[/code], [code]a > b[/code] and [code]a < b[/code] respectively. + This visual shader node has six input ports. Port 1 and 2 provide the two floating point numbers [code]a[/code] and [code]b[/code] that will be compared. Port 3 is the tolerance, which allows similar floating point number to be considered equal. Ports 4 to 6 are the possible outputs, returned if [code]a == b[/code], [code]a > b[/code], or [code]a < b[/code] respectively. </description> <tutorials> </tutorials> diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml index 82498b9ba4..92cd11d720 100644 --- a/doc/classes/Window.xml +++ b/doc/classes/Window.xml @@ -569,6 +569,9 @@ <member name="content_scale_size" type="Vector2i" setter="set_content_scale_size" getter="get_content_scale_size" default="Vector2i(0, 0)"> Base size of the content (i.e. nodes that are drawn inside the window). If non-zero, [Window]'s content will be scaled when the window is resized to a different size. </member> + <member name="content_scale_stretch" type="int" setter="set_content_scale_stretch" getter="get_content_scale_stretch" enum="Window.ContentScaleStretch" default="0"> + The policy to use to determine the final scale factor for 2D elements. This affects how [member content_scale_factor] is applied, in addition to the automatic scale factor determined by [member content_scale_size]. + </member> <member name="current_screen" type="int" setter="set_current_screen" getter="get_current_screen"> The screen the window is currently on. </member> @@ -840,6 +843,12 @@ <constant name="CONTENT_SCALE_ASPECT_EXPAND" value="4" enum="ContentScaleAspect"> The content's aspect will be preserved. If the target size has different aspect from the base one, the content will stay in the top-left corner and add an extra visible area in the stretched space. </constant> + <constant name="CONTENT_SCALE_STRETCH_FRACTIONAL" value="0" enum="ContentScaleStretch"> + The content will be stretched according to a fractional factor. This fills all the space available in the window, but allows "pixel wobble" to occur due to uneven pixel scaling. + </constant> + <constant name="CONTENT_SCALE_STRETCH_INTEGER" value="1" enum="ContentScaleStretch"> + The content will be stretched only according to an integer factor, preserving sharp pixels. This may leave a black background visible on the window's edges depending on the window size. + </constant> <constant name="LAYOUT_DIRECTION_INHERITED" value="0" enum="LayoutDirection"> Automatic layout direction, determined from the parent window layout direction. </constant> diff --git a/doc/classes/int.xml b/doc/classes/int.xml index bc0da03e98..914cf75929 100644 --- a/doc/classes/int.xml +++ b/doc/classes/int.xml @@ -390,7 +390,7 @@ <operator name="operator ~"> <return type="int" /> <description> - Performs the bitwise [code]NOT[/code] operation on the [int]. Due to [url=https://en.wikipedia.org/wiki/Two%27s_complement/]2's complement[/url], it's effectively equal to [code]-(int + 1)[/code]. + Performs the bitwise [code]NOT[/code] operation on the [int]. Due to [url=https://en.wikipedia.org/wiki/Two%27s_complement]2's complement[/url], it's effectively equal to [code]-(int + 1)[/code]. [codeblock] print(~4) # Prints -5 print(~(-7)) # Prints 6 diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp index aa6319f0ef..a36004209b 100644 --- a/drivers/gles3/storage/material_storage.cpp +++ b/drivers/gles3/storage/material_storage.cpp @@ -1957,14 +1957,16 @@ void MaterialStorage::global_shader_parameters_load_settings(bool p_load_texture if (gvtype >= RS::GLOBAL_VAR_TYPE_SAMPLER2D) { //textire if (!p_load_textures) { - value = RID(); continue; } String path = value; - Ref<Resource> resource = ResourceLoader::load(path); - ERR_CONTINUE(resource.is_null()); - value = resource; + if (path.is_empty()) { + value = RID(); + } else { + Ref<Resource> resource = ResourceLoader::load(path); + value = resource; + } } if (global_shader_uniforms.variables.has(name)) { diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp index a80b7d449e..3b15cf60f7 100644 --- a/drivers/unix/file_access_unix.cpp +++ b/drivers/unix/file_access_unix.cpp @@ -313,19 +313,19 @@ uint64_t FileAccessUnix::_get_modified_time(const String &p_file) { } } -uint32_t FileAccessUnix::_get_unix_permissions(const String &p_file) { +BitField<FileAccess::UnixPermissionFlags> FileAccessUnix::_get_unix_permissions(const String &p_file) { String file = fix_path(p_file); struct stat status = {}; int err = stat(file.utf8().get_data(), &status); if (!err) { - return status.st_mode & 0x7FF; //only permissions + return status.st_mode & 0xFFF; //only permissions } else { ERR_FAIL_V_MSG(0, "Failed to get unix permissions for: " + p_file + "."); } } -Error FileAccessUnix::_set_unix_permissions(const String &p_file, uint32_t p_permissions) { +Error FileAccessUnix::_set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) { String file = fix_path(p_file); int err = chmod(file.utf8().get_data(), p_permissions); @@ -336,6 +336,74 @@ Error FileAccessUnix::_set_unix_permissions(const String &p_file, uint32_t p_per return FAILED; } +bool FileAccessUnix::_get_hidden_attribute(const String &p_file) { +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + String file = fix_path(p_file); + + struct stat st = {}; + int err = stat(file.utf8().get_data(), &st); + ERR_FAIL_COND_V_MSG(err, false, "Failed to get attributes for: " + p_file); + + return (st.st_flags & UF_HIDDEN); +#else + return false; +#endif +} + +Error FileAccessUnix::_set_hidden_attribute(const String &p_file, bool p_hidden) { +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + String file = fix_path(p_file); + + struct stat st = {}; + int err = stat(file.utf8().get_data(), &st); + ERR_FAIL_COND_V_MSG(err, FAILED, "Failed to get attributes for: " + p_file); + + if (p_hidden) { + err = chflags(file.utf8().get_data(), st.st_flags | UF_HIDDEN); + } else { + err = chflags(file.utf8().get_data(), st.st_flags & ~UF_HIDDEN); + } + ERR_FAIL_COND_V_MSG(err, FAILED, "Failed to set attributes for: " + p_file); + return OK; +#else + return ERR_UNAVAILABLE; +#endif +} + +bool FileAccessUnix::_get_read_only_attribute(const String &p_file) { +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + String file = fix_path(p_file); + + struct stat st = {}; + int err = stat(file.utf8().get_data(), &st); + ERR_FAIL_COND_V_MSG(err, false, "Failed to get attributes for: " + p_file); + + return st.st_flags & UF_IMMUTABLE; +#else + return false; +#endif +} + +Error FileAccessUnix::_set_read_only_attribute(const String &p_file, bool p_ro) { +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + String file = fix_path(p_file); + + struct stat st = {}; + int err = stat(file.utf8().get_data(), &st); + ERR_FAIL_COND_V_MSG(err, FAILED, "Failed to get attributes for: " + p_file); + + if (p_ro) { + err = chflags(file.utf8().get_data(), st.st_flags | UF_IMMUTABLE); + } else { + err = chflags(file.utf8().get_data(), st.st_flags & ~UF_IMMUTABLE); + } + ERR_FAIL_COND_V_MSG(err, FAILED, "Failed to set attributes for: " + p_file); + return OK; +#else + return ERR_UNAVAILABLE; +#endif +} + void FileAccessUnix::close() { _close(); } diff --git a/drivers/unix/file_access_unix.h b/drivers/unix/file_access_unix.h index 79c4e73636..2bfac27c4f 100644 --- a/drivers/unix/file_access_unix.h +++ b/drivers/unix/file_access_unix.h @@ -79,8 +79,13 @@ public: virtual bool file_exists(const String &p_path) override; ///< return true if a file exists virtual uint64_t _get_modified_time(const String &p_file) override; - virtual uint32_t _get_unix_permissions(const String &p_file) override; - virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) override; + virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override; + virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override; + + virtual bool _get_hidden_attribute(const String &p_file) override; + virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override; + virtual bool _get_read_only_attribute(const String &p_file) override; + virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override; virtual void close() override; diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp index 1ed6839dd7..a55abe6a82 100644 --- a/drivers/vulkan/rendering_device_vulkan.cpp +++ b/drivers/vulkan/rendering_device_vulkan.cpp @@ -5905,6 +5905,64 @@ void RenderingDeviceVulkan::uniform_set_set_invalidation_callback(RID p_uniform_ us->invalidated_callback_userdata = p_userdata; } +Error RenderingDeviceVulkan::buffer_copy(RID p_src_buffer, RID p_dst_buffer, uint32_t p_src_offset, uint32_t p_dst_offset, uint32_t p_size, BitField<BarrierMask> p_post_barrier) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V_MSG(draw_list, ERR_INVALID_PARAMETER, + "Copying buffers is forbidden during creation of a draw list"); + ERR_FAIL_COND_V_MSG(compute_list, ERR_INVALID_PARAMETER, + "Copying buffers is forbidden during creation of a compute list"); + + // This method assumes the barriers have been pushed prior to being called, therefore no barriers are pushed + // for the source or destination buffers before performing the copy. These masks are effectively ignored. + VkPipelineShaderStageCreateFlags src_stage_mask = 0; + VkAccessFlags src_access_mask = 0; + Buffer *src_buffer = _get_buffer_from_owner(p_src_buffer, src_stage_mask, src_access_mask, BARRIER_MASK_NO_BARRIER); + if (!src_buffer) { + ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Source buffer argument is not a valid buffer of any type."); + } + + VkPipelineStageFlags dst_stage_mask = 0; + VkAccessFlags dst_access = 0; + if (p_post_barrier.has_flag(BARRIER_MASK_TRANSFER)) { + // If the post barrier mask defines it, we indicate the destination buffer will require a barrier with these flags set + // after the copy command is queued. + dst_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT; + dst_access = VK_ACCESS_TRANSFER_WRITE_BIT; + } + + Buffer *dst_buffer = _get_buffer_from_owner(p_dst_buffer, dst_stage_mask, dst_access, p_post_barrier); + if (!dst_buffer) { + ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Destination buffer argument is not a valid buffer of any type."); + } + + // Validate the copy's dimensions for both buffers. + ERR_FAIL_COND_V_MSG((p_size + p_src_offset) > src_buffer->size, ERR_INVALID_PARAMETER, "Size is larger than the source buffer."); + ERR_FAIL_COND_V_MSG((p_size + p_dst_offset) > dst_buffer->size, ERR_INVALID_PARAMETER, "Size is larger than the destination buffer."); + + // Perform the copy. + VkBufferCopy region; + region.srcOffset = p_src_offset; + region.dstOffset = p_dst_offset; + region.size = p_size; + vkCmdCopyBuffer(frames[frame].draw_command_buffer, src_buffer->buffer, dst_buffer->buffer, 1, ®ion); + +#ifdef FORCE_FULL_BARRIER + _full_barrier(true); +#else + if (dst_stage_mask == 0) { + dst_stage_mask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + } + + // As indicated by the post barrier mask, push a new barrier. + if (p_post_barrier != RD::BARRIER_MASK_NO_BARRIER) { + _buffer_memory_barrier(dst_buffer->buffer, p_dst_offset, p_size, VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stage_mask, VK_ACCESS_TRANSFER_WRITE_BIT, dst_access, true); + } +#endif + + return OK; +} + Error RenderingDeviceVulkan::buffer_update(RID p_buffer, uint32_t p_offset, uint32_t p_size, const void *p_data, BitField<BarrierMask> p_post_barrier) { _THREAD_SAFE_METHOD_ diff --git a/drivers/vulkan/rendering_device_vulkan.h b/drivers/vulkan/rendering_device_vulkan.h index fd832312ac..edff19a70c 100644 --- a/drivers/vulkan/rendering_device_vulkan.h +++ b/drivers/vulkan/rendering_device_vulkan.h @@ -1152,6 +1152,7 @@ public: virtual bool uniform_set_is_valid(RID p_uniform_set); virtual void uniform_set_set_invalidation_callback(RID p_uniform_set, InvalidationCallback p_callback, void *p_userdata); + virtual Error buffer_copy(RID p_src_buffer, RID p_dst_buffer, uint32_t p_src_offset, uint32_t p_dst_offset, uint32_t p_size, BitField<BarrierMask> p_post_barrier = BARRIER_MASK_ALL_BARRIERS); virtual Error buffer_update(RID p_buffer, uint32_t p_offset, uint32_t p_size, const void *p_data, BitField<BarrierMask> p_post_barrier = BARRIER_MASK_ALL_BARRIERS); // Works for any buffer. virtual Error buffer_clear(RID p_buffer, uint32_t p_offset, uint32_t p_size, BitField<BarrierMask> p_post_barrier = BARRIER_MASK_ALL_BARRIERS); virtual Vector<uint8_t> buffer_get_data(RID p_buffer, uint32_t p_offset = 0, uint32_t p_size = 0); diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index c167caeb7c..c1632907eb 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -504,6 +504,10 @@ Error VulkanContext::_initialize_device_extensions() { register_requested_device_extension(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME, false); register_requested_device_extension(VK_KHR_MAINTENANCE_2_EXTENSION_NAME, false); + if (Engine::get_singleton()->is_generate_spirv_debug_info_enabled()) { + register_requested_device_extension(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME, true); + } + // TODO consider the following extensions: // - VK_KHR_spirv_1_4 // - VK_KHR_swapchain_mutable_format @@ -1700,17 +1704,6 @@ Error VulkanContext::_window_create(DisplayServer::WindowID p_window_id, Display Error err = _update_swap_chain(&window); ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); - VkSemaphoreCreateInfo semaphoreCreateInfo = { - /*sType*/ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, - /*pNext*/ nullptr, - /*flags*/ 0, - }; - - for (uint32_t i = 0; i < FRAME_LAG; i++) { - VkResult vkerr = vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &window.image_acquired_semaphores[i]); - ERR_FAIL_COND_V(vkerr, ERR_CANT_CREATE); - } - windows[p_window_id] = window; return OK; } @@ -1760,9 +1753,6 @@ VkFramebuffer VulkanContext::window_get_framebuffer(DisplayServer::WindowID p_wi void VulkanContext::window_destroy(DisplayServer::WindowID p_window_id) { ERR_FAIL_COND(!windows.has(p_window_id)); _clean_up_swap_chain(&windows[p_window_id]); - for (uint32_t i = 0; i < FRAME_LAG; i++) { - vkDestroySemaphore(device, windows[p_window_id].image_acquired_semaphores[i], nullptr); - } vkDestroySurfaceKHR(inst, windows[p_window_id].surface, nullptr); windows.erase(p_window_id); @@ -1792,6 +1782,17 @@ Error VulkanContext::_clean_up_swap_chain(Window *window) { if (separate_present_queue) { vkDestroyCommandPool(device, window->present_cmd_pool, nullptr); } + + for (uint32_t i = 0; i < FRAME_LAG; i++) { + // Destroy the semaphores now (we'll re-create it later if we have to). + // We must do this because the semaphore cannot be reused if it's in a signaled state + // (which happens if vkAcquireNextImageKHR returned VK_ERROR_OUT_OF_DATE_KHR or VK_SUBOPTIMAL_KHR) + // The only way to reset it would be to present the swapchain... the one we just destroyed. + // And the API has no way to "unsignal" the semaphore. + vkDestroySemaphore(device, window->image_acquired_semaphores[i], nullptr); + window->image_acquired_semaphores[i] = 0; + } + return OK; } @@ -2175,6 +2176,17 @@ Error VulkanContext::_update_swap_chain(Window *window) { // Reset current buffer. window->current_buffer = 0; + VkSemaphoreCreateInfo semaphoreCreateInfo = { + /*sType*/ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + /*pNext*/ nullptr, + /*flags*/ 0, + }; + + for (uint32_t i = 0; i < FRAME_LAG; i++) { + VkResult vkerr = vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &window->image_acquired_semaphores[i]); + ERR_FAIL_COND_V(vkerr, ERR_CANT_CREATE); + } + return OK; } diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp index 6e69743d4e..c1e0c3fb60 100644 --- a/drivers/windows/file_access_windows.cpp +++ b/drivers/windows/file_access_windows.cpp @@ -381,14 +381,62 @@ uint64_t FileAccessWindows::_get_modified_time(const String &p_file) { } } -uint32_t FileAccessWindows::_get_unix_permissions(const String &p_file) { +BitField<FileAccess::UnixPermissionFlags> FileAccessWindows::_get_unix_permissions(const String &p_file) { return 0; } -Error FileAccessWindows::_set_unix_permissions(const String &p_file, uint32_t p_permissions) { +Error FileAccessWindows::_set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) { return ERR_UNAVAILABLE; } +bool FileAccessWindows::_get_hidden_attribute(const String &p_file) { + String file = fix_path(p_file); + + DWORD attrib = GetFileAttributesW((LPCWSTR)file.utf16().get_data()); + ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, false, "Failed to get attributes for: " + p_file); + return (attrib & FILE_ATTRIBUTE_HIDDEN); +} + +Error FileAccessWindows::_set_hidden_attribute(const String &p_file, bool p_hidden) { + String file = fix_path(p_file); + + DWORD attrib = GetFileAttributesW((LPCWSTR)file.utf16().get_data()); + ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, FAILED, "Failed to get attributes for: " + p_file); + BOOL ok; + if (p_hidden) { + ok = SetFileAttributesW((LPCWSTR)file.utf16().get_data(), attrib | FILE_ATTRIBUTE_HIDDEN); + } else { + ok = SetFileAttributesW((LPCWSTR)file.utf16().get_data(), attrib & ~FILE_ATTRIBUTE_HIDDEN); + } + ERR_FAIL_COND_V_MSG(!ok, FAILED, "Failed to set attributes for: " + p_file); + + return OK; +} + +bool FileAccessWindows::_get_read_only_attribute(const String &p_file) { + String file = fix_path(p_file); + + DWORD attrib = GetFileAttributesW((LPCWSTR)file.utf16().get_data()); + ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, false, "Failed to get attributes for: " + p_file); + return (attrib & FILE_ATTRIBUTE_READONLY); +} + +Error FileAccessWindows::_set_read_only_attribute(const String &p_file, bool p_ro) { + String file = fix_path(p_file); + + DWORD attrib = GetFileAttributesW((LPCWSTR)file.utf16().get_data()); + ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, FAILED, "Failed to get attributes for: " + p_file); + BOOL ok; + if (p_ro) { + ok = SetFileAttributesW((LPCWSTR)file.utf16().get_data(), attrib | FILE_ATTRIBUTE_READONLY); + } else { + ok = SetFileAttributesW((LPCWSTR)file.utf16().get_data(), attrib & ~FILE_ATTRIBUTE_READONLY); + } + ERR_FAIL_COND_V_MSG(!ok, FAILED, "Failed to set attributes for: " + p_file); + + return OK; +} + void FileAccessWindows::close() { _close(); } diff --git a/drivers/windows/file_access_windows.h b/drivers/windows/file_access_windows.h index 13c881e562..73143009fc 100644 --- a/drivers/windows/file_access_windows.h +++ b/drivers/windows/file_access_windows.h @@ -80,8 +80,13 @@ public: virtual bool file_exists(const String &p_name) override; ///< return true if a file exists uint64_t _get_modified_time(const String &p_file) override; - virtual uint32_t _get_unix_permissions(const String &p_file) override; - virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) override; + virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override; + virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override; + + virtual bool _get_hidden_attribute(const String &p_file) override; + virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override; + virtual bool _get_read_only_attribute(const String &p_file) override; + virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override; virtual void close() override; diff --git a/editor/debugger/editor_debugger_inspector.cpp b/editor/debugger/editor_debugger_inspector.cpp index 1e9b7c2c60..b2fd84d2ed 100644 --- a/editor/debugger/editor_debugger_inspector.cpp +++ b/editor/debugger/editor_debugger_inspector.cpp @@ -232,7 +232,7 @@ void EditorDebuggerInspector::add_stack_variable(const Array &p_array) { PropertyHint h = PROPERTY_HINT_NONE; String hs; - if (var.var_type == Variant::OBJECT) { + if (var.var_type == Variant::OBJECT && v) { v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id(); h = PROPERTY_HINT_OBJECT_ID; hs = "Object"; diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp index 1bc9f00f08..4c2f6b3176 100644 --- a/editor/editor_log.cpp +++ b/editor/editor_log.cpp @@ -212,7 +212,7 @@ void EditorLog::clear() { _clear_request(); } -void EditorLog::_process_message(const String &p_msg, MessageType p_type) { +void EditorLog::_process_message(const String &p_msg, MessageType p_type, bool p_clear) { if (messages.size() > 0 && messages[messages.size() - 1].text == p_msg && messages[messages.size() - 1].type == p_type) { // If previous message is the same as the new one, increase previous count rather than adding another // instance to the messages list. @@ -222,7 +222,7 @@ void EditorLog::_process_message(const String &p_msg, MessageType p_type) { _add_log_line(previous, collapse); } else { // Different message to the previous one received. - LogMessage message(p_msg, p_type); + LogMessage message(p_msg, p_type, p_clear); _add_log_line(message); messages.push_back(message); } @@ -237,9 +237,10 @@ void EditorLog::add_message(const String &p_msg, MessageType p_type) { // search functionality (see the comments on the PR above for more details). This behavior // also matches that of other IDE's. Vector<String> lines = p_msg.split("\n", true); + int line_count = lines.size(); - for (int i = 0; i < lines.size(); i++) { - _process_message(lines[i], p_type); + for (int i = 0; i < line_count; i++) { + _process_message(lines[i], p_type, i == line_count - 1); } } @@ -338,7 +339,9 @@ void EditorLog::_add_log_line(LogMessage &p_message, bool p_replace_previous) { } else { log->add_text(p_message.text); } - log->pop_all(); // Pop all unclosed tags. + if (p_message.clear || p_message.type != MSG_TYPE_STD_RICH) { + log->pop_all(); // Pop all unclosed tags. + } log->add_newline(); if (p_replace_previous) { diff --git a/editor/editor_log.h b/editor/editor_log.h index b875066afa..07f3a25c3e 100644 --- a/editor/editor_log.h +++ b/editor/editor_log.h @@ -60,12 +60,14 @@ private: String text; MessageType type; int count = 1; + bool clear = true; LogMessage() {} - LogMessage(const String p_text, MessageType p_type) : + LogMessage(const String p_text, MessageType p_type, bool p_clear) : text(p_text), - type(p_type) { + type(p_type), + clear(p_clear) { } }; @@ -166,7 +168,7 @@ private: void _set_search_visible(bool p_visible); void _search_changed(const String &p_text); - void _process_message(const String &p_msg, MessageType p_type); + void _process_message(const String &p_msg, MessageType p_type, bool p_clear); void _reset_message_counts(); void _set_collapse(bool p_collapse); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 2efd3c031e..f207418f71 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -6928,8 +6928,7 @@ EditorNode::EditorNode() { { // Register importers at the beginning, so dialogs are created with the right extensions. - Ref<ResourceImporterTexture> import_texture; - import_texture.instantiate(); + Ref<ResourceImporterTexture> import_texture = memnew(ResourceImporterTexture(true)); ResourceFormatImporter::get_singleton()->add_importer(import_texture); Ref<ResourceImporterLayeredTexture> import_cubemap; @@ -6947,8 +6946,7 @@ EditorNode::EditorNode() { import_cubemap_array->set_mode(ResourceImporterLayeredTexture::MODE_CUBEMAP_ARRAY); ResourceFormatImporter::get_singleton()->add_importer(import_cubemap_array); - Ref<ResourceImporterLayeredTexture> import_3d; - import_3d.instantiate(); + Ref<ResourceImporterLayeredTexture> import_3d = memnew(ResourceImporterLayeredTexture(true)); import_3d->set_mode(ResourceImporterLayeredTexture::MODE_3D); ResourceFormatImporter::get_singleton()->add_importer(import_3d); @@ -6988,12 +6986,10 @@ EditorNode::EditorNode() { import_shader_file.instantiate(); ResourceFormatImporter::get_singleton()->add_importer(import_shader_file); - Ref<ResourceImporterScene> import_scene; - import_scene.instantiate(); + Ref<ResourceImporterScene> import_scene = memnew(ResourceImporterScene(false, true)); ResourceFormatImporter::get_singleton()->add_importer(import_scene); - Ref<ResourceImporterScene> import_animation; - import_animation = Ref<ResourceImporterScene>(memnew(ResourceImporterScene(true))); + Ref<ResourceImporterScene> import_animation = memnew(ResourceImporterScene(true, true)); ResourceFormatImporter::get_singleton()->add_importer(import_animation); { @@ -7856,7 +7852,6 @@ EditorNode::EditorNode() { log = memnew(EditorLog); Button *output_button = add_bottom_panel_item(TTR("Output"), log); - output_button->set_theme_type_variation("BottomPanelButton"); log->set_tool_button(output_button); center_split->connect("resized", callable_mp(this, &EditorNode::_vp_resized)); @@ -8008,8 +8003,8 @@ EditorNode::EditorNode() { vcs_actions_menu = VersionControlEditorPlugin::get_singleton()->get_version_control_actions_panel(); vcs_actions_menu->set_name("Version Control"); vcs_actions_menu->connect("index_pressed", callable_mp(this, &EditorNode::_version_control_menu_option)); - vcs_actions_menu->add_item(TTR("Create Version Control Metadata"), RUN_VCS_METADATA); - vcs_actions_menu->add_item(TTR("Version Control Settings"), RUN_VCS_SETTINGS); + vcs_actions_menu->add_item(TTR("Create Version Control Metadata..."), RUN_VCS_METADATA); + vcs_actions_menu->add_item(TTR("Version Control Settings..."), RUN_VCS_SETTINGS); project_menu->add_child(vcs_actions_menu); project_menu->set_item_submenu(project_menu->get_item_index(VCS_MENU), "Version Control"); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 7952efff97..f904ed875d 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -914,7 +914,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("icon_pressed_color", "Button", icon_pressed_color); theme->set_color("icon_disabled_color", "Button", icon_disabled_color); - theme->set_constant("h_separation", "Button", 2 * EDSCALE); + theme->set_constant("h_separation", "Button", 4 * EDSCALE); theme->set_constant("outline_size", "Button", 0); const float ACTION_BUTTON_EXTRA_MARGIN = 32 * EDSCALE; @@ -1479,9 +1479,6 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_stylebox("panel", "TabContainer", style_content_panel); // Bottom panel. - theme->set_type_variation("BottomPanelButton", "Button"); - // Add separation for the warning/error icon. - theme->set_constant("h_separation", "BottomPanelButton", 6 * EDSCALE); Ref<StyleBoxFlat> style_bottom_panel = style_content_panel->duplicate(); style_bottom_panel->set_corner_radius_all(corner_radius * EDSCALE); theme->set_stylebox("BottomPanel", "EditorStyles", style_bottom_panel); diff --git a/editor/icons/CurveTexture.svg b/editor/icons/CurveTexture.svg index 50232fdc4a..a5524f145c 100644 --- a/editor/icons/CurveTexture.svg +++ b/editor/icons/CurveTexture.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v9.16A3 3 0 0 1 2 11c.331 0 .666-.008 1-.014V3h10v1.135a3 3 0 0 1 2-.006V2a1 1 0 0 0-1-1H2zm7 4v1H8v1H6v1H5v1H4v1h4.39c1.113-.567 1.968-1.454 2.61-3.473V6h-1V5H9zm4.967.988a1 1 0 0 0-.928.739c-.927 3.246-2.636 4.682-4.652 5.466C6.37 12.978 4 13 2 13a1 1 0 1 0 0 2c2 0 4.63.024 7.113-.941 2.484-.966 4.775-3.03 5.848-6.784a1 1 0 0 0-.994-1.287z" fill="#e0e0e0"/></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v9.16A3 3 0 0 1 2 11h1V3h10v1.135a3 3 0 0 1 2 0V2a1 1 0 0 0-1-1zm7 4v1H8v1H6v1H5v1H4v1h4.39c1.113-.567 1.968-1.454 2.61-3.473V6h-1V5H9zm4.039 1.727c-.927 3.246-2.636 4.682-4.652 5.466C6.37 12.978 4 13 2 13a1 1 0 1 0 0 2c2 0 4.63.024 7.113-.941 2.484-.966 4.775-3.03 5.848-6.784a1 1 0 0 0-1.922-.548z" fill="#e0e0e0"/></svg> diff --git a/editor/icons/CurveXYZTexture.svg b/editor/icons/CurveXYZTexture.svg new file mode 100644 index 0000000000..e376dd434b --- /dev/null +++ b/editor/icons/CurveXYZTexture.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v10a2 2 0 0 1 .75-.1Q3 11.9 3 10.62V3h10v6.5a2 2 0 0 1 2 0V2a1 1 0 0 0-1-1zm7 4v1H8v1H6v1H5v1H4v.5a2 2 0 0 1 2.7.5h1.2a2 2 0 0 1 3.2 0h.9V8h-1V6h-1V5z" fill="#e0e0e0"/><g stroke-width="1.6" stroke-linecap="round" fill="none"><path d="M2 14.2q2.5 0 3-3" stroke="#ff5f5f"/><path d="M6.5 14.2q2.5 0 3-3" stroke="#5fff97"/><path d="M11 14.2q2.5 0 3-3" stroke="#5fb2ff"/></g></svg> diff --git a/editor/icons/ImageTexture.svg b/editor/icons/ImageTexture.svg index 25d4b53d00..17fc57d31d 100644 --- a/editor/icons/ImageTexture.svg +++ b/editor/icons/ImageTexture.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m2 1a1 1 0 0 0 -1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-12a1 1 0 0 0 -1-1zm1 2h10v8h-10zm6 2v1h-1v1h-2v1h-1v1h-1v1h2 2 2 2v-2h-1v-2h-1v-1z" fill="#e0e0e0"/></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zm1 2h10v8H3zm6 2v1H8v1H6v1H5v1H4v1h8V8h-1V6h-1V5z" fill="#e0e0e0"/></svg> diff --git a/editor/icons/NavigationAgent2D.svg b/editor/icons/NavigationAgent2D.svg index 09b9b35262..ad11096afc 100644 --- a/editor/icons/NavigationAgent2D.svg +++ b/editor/icons/NavigationAgent2D.svg @@ -1 +1 @@ -<svg height="16" width="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M8 1v2.5a2.5 2.5 0 0 1 0 5V15c2-3 5.007-6.03 5-9s-2-5-5-5z" fill="#8da5f3"/><path d="M8 1C5 1 3 3 3 6s3 6 5 9V8.5a1 1 0 0 1 0-5z" fill="#e0e0e0"/></svg> +<svg height="16" width="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M8 1v2.5a2.5 2.5 0 0 1 0 5V15c2-3 5-6 5-9s-2-5-5-5z" fill="#8da5f3"/><path d="M8 1C5 1 3 3 3 6s3 6 5 9V8.5a1 1 0 0 1 0-5z" fill="#e0e0e0"/></svg> diff --git a/editor/icons/NavigationAgent3D.svg b/editor/icons/NavigationAgent3D.svg index cc34201f9b..6f887611a9 100644 --- a/editor/icons/NavigationAgent3D.svg +++ b/editor/icons/NavigationAgent3D.svg @@ -1 +1 @@ -<svg height="16" width="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M8 1.086c-.845.156-1.476.514-1.94.988L4.05 4.059Q4 8 7 9.879V15l1-1z" fill="#e0e0e0" fill-opacity=".5"/><path d="M7 3C4 3 3 5 3 7s2 5 4 8c.338-.507.672-1.012 1-1.514v-4.76a2 2 0 1 1 0-3.453V3.084A5.663 5.663 0 0 0 7 3z" fill="#e0e0e0"/><g fill="#fc7f7f"><path d="M9 1c-.363 0-.695.03-1 .086V14l1-1c2-3 4-6 4-8s-1-4-4-4z" fill-opacity=".5"/><path d="M8 3.084v2.19a2 2 0 0 1 0 3.453v4.76c1.615-2.47 3-4.825 3-6.487 0-1.759-.774-3.517-3-3.916z"/></g></svg> +<svg height="16" width="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M8 1C5 1 3 3 3 6s3 6 5 9V8.5a1 1 0 0 1 0-5z" fill="#e0e0e0"/><path d="M8 1v2.5a2.5 2.5 0 0 1 0 5V15c2-3 5-6 5-9s-2-5-5-5z" fill="#fc7f7f"/></svg> diff --git a/editor/icons/NavigationObstacle3D.svg b/editor/icons/NavigationObstacle3D.svg index 2f6f198aab..52080bf3fc 100644 --- a/editor/icons/NavigationObstacle3D.svg +++ b/editor/icons/NavigationObstacle3D.svg @@ -1 +1 @@ -<svg height="16" width="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0"><path d="M4.607 8.379C2.81 9.307 1 10.45 1 11c0 1 6 4 7 4v-2c-2.5 0-5-1-4-3z" fill-opacity=".5"/><path d="M8 .875c-.375 0-.75.375-1 1.125l-3 8c-1 2 1.5 3 4 3V9.25c-.875 0-1.75-.25-2.5-.75l1-3.5c.5.25 1 .375 1.5.375z"/></g><g fill="#fc7f7f"><path d="M11.393 8.379 12 10c1.002 2.005-1.512 3.005-4.018 3v1.998L8 15c1 0 7-3 7-4 0-.549-1.81-1.693-3.607-2.621z" fill-opacity=".5"/><path d="m8 .875-.018.002v4.498A3.323 3.323 0 0 0 9.5 5l1 3.5a4.508 4.508 0 0 1-2.518.75V13c2.506.005 5.02-.995 4.018-3L9 2C8.75 1.25 8.375.875 8 .875z"/></g></svg> +<svg height="16" width="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M8 .875C7.375.875 6.75 1.25 6.5 2l-3 10H8V9H5.5l1-4H8zM2 13c-1 0-1 2 0 2h6v-2z" fill="#e0e0e0"/><path d="M8 .875V5h1.5l1 4H8v3h4.5l-3-10C9.25 1.25 8.625.875 8 .875zM8 13v2h6c1 0 1-2 0-2z" fill="#fc7f7f"/></svg> diff --git a/editor/icons/PortableCompressedTexture2D.svg b/editor/icons/PortableCompressedTexture2D.svg new file mode 100644 index 0000000000..3f63ddae51 --- /dev/null +++ b/editor/icons/PortableCompressedTexture2D.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h7v-4H3V3h6V1zm6 5v1H6v1H5v1H4v1h5V6zm3-5h4v4h-4Zm0 5h4v4h-4Zm0 5h4v4h-4Z" fill="#e0e0e0"/></svg> diff --git a/editor/icons/ProxyTexture.svg b/editor/icons/ProxyTexture.svg deleted file mode 100644 index 5fe39f4da8..0000000000 --- a/editor/icons/ProxyTexture.svg +++ /dev/null @@ -1 +0,0 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M1 1v4h4V1zm6 0v2h6v8H7v4h7a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zm2 4v1H8v1H7v3h5V8h-1V6h-1V5zM1 6v4h4V6zm0 5v4h4v-4z" fill="#e0e0e0"/></svg> diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp index 3c27864eff..46882e95cb 100644 --- a/editor/import/resource_importer_layered_texture.cpp +++ b/editor/import/resource_importer_layered_texture.cpp @@ -474,12 +474,19 @@ bool ResourceImporterLayeredTexture::are_import_settings_valid(const String &p_p ResourceImporterLayeredTexture *ResourceImporterLayeredTexture::singleton = nullptr; -ResourceImporterLayeredTexture::ResourceImporterLayeredTexture() { - singleton = this; +ResourceImporterLayeredTexture::ResourceImporterLayeredTexture(bool p_singleton) { + // This should only be set through the EditorNode. + if (p_singleton) { + singleton = this; + } + mode = MODE_CUBEMAP; } ResourceImporterLayeredTexture::~ResourceImporterLayeredTexture() { + if (singleton == this) { + singleton = nullptr; + } } void ResourceImporterLayeredTexture::_check_compress_ctex(const String &p_source_file, Ref<LayeredTextureImport> r_texture_import) { diff --git a/editor/import/resource_importer_layered_texture.h b/editor/import/resource_importer_layered_texture.h index 52fd37639d..5a21651de3 100644 --- a/editor/import/resource_importer_layered_texture.h +++ b/editor/import/resource_importer_layered_texture.h @@ -119,7 +119,7 @@ public: void set_mode(Mode p_mode) { mode = p_mode; } - ResourceImporterLayeredTexture(); + ResourceImporterLayeredTexture(bool p_singleton = false); ~ResourceImporterLayeredTexture(); }; diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index b0291f3a93..2e422d8c27 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -2607,15 +2607,28 @@ void ResourceImporterScene::ResourceImporterScene::show_advanced_options(const S SceneImportSettings::get_singleton()->open_settings(p_path, animation_importer); } -ResourceImporterScene::ResourceImporterScene(bool p_animation_import) { - if (p_animation_import) { - animation_singleton = this; - } else { - scene_singleton = this; +ResourceImporterScene::ResourceImporterScene(bool p_animation_import, bool p_singleton) { + // This should only be set through the EditorNode. + if (p_singleton) { + if (p_animation_import) { + animation_singleton = this; + } else { + scene_singleton = this; + } } + animation_importer = p_animation_import; } +ResourceImporterScene::~ResourceImporterScene() { + if (animation_singleton == this) { + animation_singleton = nullptr; + } + if (scene_singleton == this) { + scene_singleton = nullptr; + } +} + void ResourceImporterScene::add_importer(Ref<EditorSceneFormatImporter> p_importer, bool p_first_priority) { ERR_FAIL_COND(p_importer.is_null()); if (p_first_priority) { diff --git a/editor/import/resource_importer_scene.h b/editor/import/resource_importer_scene.h index a66fd034f8..7a07a5f646 100644 --- a/editor/import/resource_importer_scene.h +++ b/editor/import/resource_importer_scene.h @@ -296,7 +296,8 @@ public: virtual bool can_import_threaded() const override { return false; } - ResourceImporterScene(bool p_animation_import = false); + ResourceImporterScene(bool p_animation_import = false, bool p_singleton = false); + ~ResourceImporterScene(); template <class M> static Vector<Ref<Shape3D>> get_collision_shapes(const Ref<ImporterMesh> &p_mesh, const M &p_options, float p_applied_root_scale); diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp index 114ba5653a..8eac5ec323 100644 --- a/editor/import/resource_importer_texture.cpp +++ b/editor/import/resource_importer_texture.cpp @@ -789,10 +789,12 @@ bool ResourceImporterTexture::are_import_settings_valid(const String &p_path) co ResourceImporterTexture *ResourceImporterTexture::singleton = nullptr; -ResourceImporterTexture::ResourceImporterTexture() { - if (!singleton) { +ResourceImporterTexture::ResourceImporterTexture(bool p_singleton) { + // This should only be set through the EditorNode. + if (p_singleton) { singleton = this; } + CompressedTexture2D::request_3d_callback = _texture_reimport_3d; CompressedTexture2D::request_roughness_callback = _texture_reimport_roughness; CompressedTexture2D::request_normal_callback = _texture_reimport_normal; diff --git a/editor/import/resource_importer_texture.h b/editor/import/resource_importer_texture.h index c2bdbb6fa2..f2539a8f52 100644 --- a/editor/import/resource_importer_texture.h +++ b/editor/import/resource_importer_texture.h @@ -107,7 +107,7 @@ public: virtual bool are_import_settings_valid(const String &p_path) const override; virtual String get_import_settings_string() const override; - ResourceImporterTexture(); + ResourceImporterTexture(bool p_singleton = false); ~ResourceImporterTexture(); }; diff --git a/editor/plugin_config_dialog.cpp b/editor/plugin_config_dialog.cpp index 97398b8b69..7d7b156bd0 100644 --- a/editor/plugin_config_dialog.cpp +++ b/editor/plugin_config_dialog.cpp @@ -35,6 +35,7 @@ #include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/editor_scale.h" +#include "editor/gui/editor_validation_panel.h" #include "editor/project_settings_editor.h" #include "scene/gui/grid_container.h" @@ -96,52 +97,28 @@ void PluginConfigDialog::_on_canceled() { _clear_fields(); } -void PluginConfigDialog::_on_language_changed(const int) { - _on_required_text_changed(String()); -} - -void PluginConfigDialog::_on_required_text_changed(const String &) { +void PluginConfigDialog::_on_required_text_changed() { int lang_idx = script_option_edit->get_selected(); String ext = ScriptServer::get_language(lang_idx)->get_extension(); - Ref<Texture2D> valid_icon = get_theme_icon(SNAME("StatusSuccess"), SNAME("EditorIcons")); - Ref<Texture2D> invalid_icon = get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")); - - // Set variables to assume all is valid - bool is_valid = true; - name_validation->set_texture(valid_icon); - subfolder_validation->set_texture(valid_icon); - script_validation->set_texture(valid_icon); - name_validation->set_tooltip_text(""); - subfolder_validation->set_tooltip_text(""); - script_validation->set_tooltip_text(""); - - // Change valid status to invalid depending on conditions. - Vector<String> errors; if (name_edit->get_text().is_empty()) { - is_valid = false; - name_validation->set_texture(invalid_icon); - name_validation->set_tooltip_text(TTR("Plugin name cannot be blank.")); + validation_panel->set_message(MSG_ID_PLUGIN, TTR("Plugin name cannot be blank."), EditorValidationPanel::MSG_ERROR); } if ((!script_edit->get_text().get_extension().is_empty() && script_edit->get_text().get_extension() != ext) || script_edit->get_text().ends_with(".")) { - is_valid = false; - script_validation->set_texture(invalid_icon); - script_validation->set_tooltip_text(vformat(TTR("Script extension must match chosen language extension (.%s)."), ext)); + validation_panel->set_message(MSG_ID_SCRIPT, vformat(TTR("Script extension must match chosen language extension (.%s)."), ext), EditorValidationPanel::MSG_ERROR); } - if (!subfolder_edit->get_text().is_empty() && !subfolder_edit->get_text().is_valid_filename()) { - is_valid = false; - subfolder_validation->set_texture(invalid_icon); - subfolder_validation->set_tooltip_text(TTR("Subfolder name is not a valid folder name.")); - } else { - String path = "res://addons/" + _get_subfolder(); - if (!_edit_mode && DirAccess::exists(path)) { // Only show this error if in "create" mode. - is_valid = false; - subfolder_validation->set_texture(invalid_icon); - subfolder_validation->set_tooltip_text(TTR("Subfolder cannot be one which already exists.")); + if (subfolder_edit->is_visible()) { + if (!subfolder_edit->get_text().is_empty() && !subfolder_edit->get_text().is_valid_filename()) { + validation_panel->set_message(MSG_ID_SUBFOLDER, TTR("Subfolder name is not a valid folder name."), EditorValidationPanel::MSG_ERROR); + } else { + String path = "res://addons/" + _get_subfolder(); + if (!_edit_mode && DirAccess::exists(path)) { // Only show this error if in "create" mode. + validation_panel->set_message(MSG_ID_SUBFOLDER, TTR("Subfolder cannot be one which already exists."), EditorValidationPanel::MSG_ERROR); + } } + } else { + validation_panel->set_message(MSG_ID_SUBFOLDER, "", EditorValidationPanel::MSG_OK); } - - get_ok_button()->set_disabled(!is_valid); } String PluginConfigDialog::_get_subfolder() { @@ -182,23 +159,20 @@ void PluginConfigDialog::config(const String &p_config_path) { _edit_mode = true; active_edit->hide(); - Object::cast_to<Label>(active_edit->get_parent()->get_child(active_edit->get_index() - 2))->hide(); + Object::cast_to<Label>(active_edit->get_parent()->get_child(active_edit->get_index() - 1))->hide(); subfolder_edit->hide(); - subfolder_validation->hide(); - Object::cast_to<Label>(subfolder_edit->get_parent()->get_child(subfolder_edit->get_index() - 2))->hide(); + Object::cast_to<Label>(subfolder_edit->get_parent()->get_child(subfolder_edit->get_index() - 1))->hide(); set_title(TTR("Edit a Plugin")); } else { _clear_fields(); _edit_mode = false; active_edit->show(); - Object::cast_to<Label>(active_edit->get_parent()->get_child(active_edit->get_index() - 2))->show(); + Object::cast_to<Label>(active_edit->get_parent()->get_child(active_edit->get_index() - 1))->show(); subfolder_edit->show(); - subfolder_validation->show(); - Object::cast_to<Label>(subfolder_edit->get_parent()->get_child(subfolder_edit->get_index() - 2))->show(); + Object::cast_to<Label>(subfolder_edit->get_parent()->get_child(subfolder_edit->get_index() - 1))->show(); set_title(TTR("Create a Plugin")); } - // Simulate text changing so the errors populate. - _on_required_text_changed(""); + validation_panel->update(); get_ok_button()->set_disabled(!_edit_mode); set_ok_button_text(_edit_mode ? TTR("Update") : TTR("Create")); @@ -218,7 +192,7 @@ PluginConfigDialog::PluginConfigDialog() { add_child(vbox); GridContainer *grid = memnew(GridContainer); - grid->set_columns(3); + grid->set_columns(2); grid->set_v_size_flags(Control::SIZE_EXPAND_FILL); vbox->add_child(grid); @@ -228,12 +202,7 @@ PluginConfigDialog::PluginConfigDialog() { name_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(name_lb); - name_validation = memnew(TextureRect); - name_validation->set_v_size_flags(Control::SIZE_SHRINK_CENTER); - grid->add_child(name_validation); - name_edit = memnew(LineEdit); - name_edit->connect("text_changed", callable_mp(this, &PluginConfigDialog::_on_required_text_changed)); name_edit->set_placeholder("MyPlugin"); name_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); grid->add_child(name_edit); @@ -244,14 +213,9 @@ PluginConfigDialog::PluginConfigDialog() { subfolder_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(subfolder_lb); - subfolder_validation = memnew(TextureRect); - subfolder_validation->set_v_size_flags(Control::SIZE_SHRINK_CENTER); - grid->add_child(subfolder_validation); - subfolder_edit = memnew(LineEdit); subfolder_edit->set_placeholder("\"my_plugin\" -> res://addons/my_plugin"); subfolder_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); - subfolder_edit->connect("text_changed", callable_mp(this, &PluginConfigDialog::_on_required_text_changed)); grid->add_child(subfolder_edit); // Description @@ -260,9 +224,6 @@ PluginConfigDialog::PluginConfigDialog() { desc_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(desc_lb); - Control *desc_spacer = memnew(Control); - grid->add_child(desc_spacer); - desc_edit = memnew(TextEdit); desc_edit->set_custom_minimum_size(Size2(400, 80) * EDSCALE); desc_edit->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY); @@ -276,9 +237,6 @@ PluginConfigDialog::PluginConfigDialog() { author_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(author_lb); - Control *author_spacer = memnew(Control); - grid->add_child(author_spacer); - author_edit = memnew(LineEdit); author_edit->set_placeholder("Godette"); author_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -290,9 +248,6 @@ PluginConfigDialog::PluginConfigDialog() { version_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(version_lb); - Control *version_spacer = memnew(Control); - grid->add_child(version_spacer); - version_edit = memnew(LineEdit); version_edit->set_placeholder("1.0"); version_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -304,9 +259,6 @@ PluginConfigDialog::PluginConfigDialog() { script_option_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(script_option_lb); - Control *script_opt_spacer = memnew(Control); - grid->add_child(script_opt_spacer); - script_option_edit = memnew(OptionButton); int default_lang = 0; for (int i = 0; i < ScriptServer::get_language_count(); i++) { @@ -318,7 +270,6 @@ PluginConfigDialog::PluginConfigDialog() { } script_option_edit->select(default_lang); grid->add_child(script_option_edit); - script_option_edit->connect("item_selected", callable_mp(this, &PluginConfigDialog::_on_language_changed)); // Plugin Script Name Label *script_lb = memnew(Label); @@ -326,12 +277,7 @@ PluginConfigDialog::PluginConfigDialog() { script_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(script_lb); - script_validation = memnew(TextureRect); - script_validation->set_v_size_flags(Control::SIZE_SHRINK_CENTER); - grid->add_child(script_validation); - script_edit = memnew(LineEdit); - script_edit->connect("text_changed", callable_mp(this, &PluginConfigDialog::_on_required_text_changed)); script_edit->set_placeholder("\"plugin.gd\" -> res://addons/my_plugin/plugin.gd"); script_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); grid->add_child(script_edit); @@ -343,12 +289,26 @@ PluginConfigDialog::PluginConfigDialog() { active_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(active_lb); - Control *active_spacer = memnew(Control); - grid->add_child(active_spacer); - active_edit = memnew(CheckBox); active_edit->set_pressed(true); grid->add_child(active_edit); + + Control *spacing = memnew(Control); + vbox->add_child(spacing); + spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE)); + + validation_panel = memnew(EditorValidationPanel); + vbox->add_child(validation_panel); + validation_panel->add_line(MSG_ID_PLUGIN, TTR("Plugin name is valid.")); + validation_panel->add_line(MSG_ID_SCRIPT, TTR("Script extension is valid.")); + validation_panel->add_line(MSG_ID_SUBFOLDER, TTR("Subfolder name is valid.")); + validation_panel->set_update_callback(callable_mp(this, &PluginConfigDialog::_on_required_text_changed)); + validation_panel->set_accept_button(get_ok_button()); + + script_option_edit->connect("item_selected", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1)); + name_edit->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1)); + subfolder_edit->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1)); + script_edit->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1)); } PluginConfigDialog::~PluginConfigDialog() { diff --git a/editor/plugin_config_dialog.h b/editor/plugin_config_dialog.h index 50b901a39e..1221d347a7 100644 --- a/editor/plugin_config_dialog.h +++ b/editor/plugin_config_dialog.h @@ -35,12 +35,21 @@ #include "scene/gui/dialogs.h" #include "scene/gui/line_edit.h" #include "scene/gui/option_button.h" +#include "scene/gui/panel_container.h" #include "scene/gui/text_edit.h" #include "scene/gui/texture_rect.h" +class EditorValidationPanel; + class PluginConfigDialog : public ConfirmationDialog { GDCLASS(PluginConfigDialog, ConfirmationDialog); + enum { + MSG_ID_PLUGIN, + MSG_ID_SUBFOLDER, + MSG_ID_SCRIPT, + }; + LineEdit *name_edit = nullptr; LineEdit *subfolder_edit = nullptr; TextEdit *desc_edit = nullptr; @@ -50,17 +59,14 @@ class PluginConfigDialog : public ConfirmationDialog { LineEdit *script_edit = nullptr; CheckBox *active_edit = nullptr; - TextureRect *name_validation = nullptr; - TextureRect *subfolder_validation = nullptr; - TextureRect *script_validation = nullptr; + EditorValidationPanel *validation_panel = nullptr; bool _edit_mode = false; void _clear_fields(); void _on_confirmed(); void _on_canceled(); - void _on_language_changed(const int p_language); - void _on_required_text_changed(const String &p_text); + void _on_required_text_changed(); String _get_subfolder(); static String _to_absolute_plugin_path(const String &p_plugin_name); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 75bb6eefe8..c9c67f3d6b 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -2282,8 +2282,8 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { if (b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) { add_node_menu->clear(); - add_node_menu->add_icon_item(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")), TTR("Add Node Here"), ADD_NODE); - add_node_menu->add_icon_item(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")), TTR("Instantiate Scene Here"), ADD_INSTANCE); + add_node_menu->add_icon_item(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")), TTR("Add Node Here..."), ADD_NODE); + add_node_menu->add_icon_item(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")), TTR("Instantiate Scene Here..."), ADD_INSTANCE); for (Node *node : SceneTreeDock::get_singleton()->get_node_clipboard()) { if (Object::cast_to<CanvasItem>(node)) { add_node_menu->add_icon_item(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons")), TTR("Paste Node(s) Here"), ADD_PASTE); diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp index 3c9bc991d3..15829a55de 100644 --- a/editor/plugins/debugger_editor_plugin.cpp +++ b/editor/plugins/debugger_editor_plugin.cpp @@ -54,7 +54,6 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(PopupMenu *p_debug_menu) { EditorDebuggerNode *debugger = memnew(EditorDebuggerNode); Button *db = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Debugger"), debugger); - db->set_theme_type_variation("BottomPanelButton"); debugger->set_tool_button(db); // Main editor debug menu. diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index d05db7aa63..65563bd1a3 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -1675,6 +1675,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { if (_edit.mode != TRANSFORM_NONE && b->is_pressed()) { cancel_transform(); + break; } if (b->is_pressed()) { @@ -2007,7 +2008,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { _edit.mode = TRANSFORM_TRANSLATE; } - if (_edit.mode == TRANSFORM_NONE) { + if (_edit.mode == TRANSFORM_NONE || _edit.numeric_input != 0 || _edit.numeric_next_decimal != 0) { return; } @@ -2145,6 +2146,43 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { return; } + if (_edit.instant) { + // In a Blender-style transform, numbers set the magnitude of the transform. + // E.g. pressing g4.5x means "translate 4.5 units along the X axis". + // Use the Unicode value because we care about the text, not the actual keycode. + // This ensures numbers work consistently across different keyboard language layouts. + bool processed = true; + Key key = k->get_physical_keycode(); + char32_t unicode = k->get_unicode(); + if (unicode >= '0' && unicode <= '9') { + uint32_t value = uint32_t(unicode - Key::KEY_0); + if (_edit.numeric_next_decimal < 0) { + _edit.numeric_input = _edit.numeric_input + value * Math::pow(10.0, _edit.numeric_next_decimal--); + } else { + _edit.numeric_input = _edit.numeric_input * 10 + value; + } + update_transform_numeric(); + } else if (unicode == '-') { + _edit.numeric_negate = !_edit.numeric_negate; + update_transform_numeric(); + } else if (unicode == '.') { + if (_edit.numeric_next_decimal == 0) { + _edit.numeric_next_decimal = -1; + } + } else if (key == Key::ENTER || key == Key::KP_ENTER || key == Key::SPACE) { + commit_transform(); + } else { + processed = false; + } + + if (processed) { + // Ignore mouse inputs once we receive a numeric input. + set_process_input(false); + accept_event(); + return; + } + } + if (EDITOR_GET("editors/3d/navigation/emulate_numpad")) { const Key code = k->get_physical_keycode(); if (code >= Key::KEY_0 && code <= Key::KEY_9) { @@ -2165,26 +2203,19 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } else { // We're actively transforming, handle keys specially TransformPlane new_plane = TRANSFORM_VIEW; - String new_message; if (ED_IS_SHORTCUT("spatial_editor/lock_transform_x", p_event)) { new_plane = TRANSFORM_X_AXIS; - new_message = TTR("X-Axis Transform."); } else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_y", p_event)) { new_plane = TRANSFORM_Y_AXIS; - new_message = TTR("Y-Axis Transform."); } else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_z", p_event)) { new_plane = TRANSFORM_Z_AXIS; - new_message = TTR("Z-Axis Transform."); } else if (_edit.mode != TRANSFORM_ROTATE) { // rotating on a plane doesn't make sense if (ED_IS_SHORTCUT("spatial_editor/lock_transform_yz", p_event)) { new_plane = TRANSFORM_YZ; - new_message = TTR("YZ-Plane Transform."); } else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xz", p_event)) { new_plane = TRANSFORM_XZ; - new_message = TTR("XZ-Plane Transform."); } else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xy", p_event)) { new_plane = TRANSFORM_XY; - new_message = TTR("XY-Plane Transform."); } } @@ -2201,8 +2232,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { _edit.plane = TRANSFORM_VIEW; spatial_editor->set_local_coords_enabled(false); } - update_transform(Input::get_singleton()->is_key_pressed(Key::SHIFT)); - set_message(new_message, 2); + if (_edit.numeric_input != 0 || _edit.numeric_next_decimal != 0) { + update_transform_numeric(); + } else { + update_transform(Input::get_singleton()->is_key_pressed(Key::SHIFT)); + } accept_event(); return; } @@ -4575,6 +4609,43 @@ void Node3DEditorViewport::commit_transform() { set_message(""); } +void Node3DEditorViewport::apply_transform(Vector3 p_motion, double p_snap) { + bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); + List<Node *> &selection = editor_selection->get_selected_node_list(); + for (Node *E : selection) { + Node3D *sp = Object::cast_to<Node3D>(E); + if (!sp) { + continue; + } + + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (!se) { + continue; + } + + if (sp->has_meta("_edit_lock_")) { + continue; + } + + if (se->gizmo.is_valid()) { + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; + Transform3D new_xform = _compute_transform(_edit.mode, se->original * xform, xform, p_motion, p_snap, local_coords, _edit.plane != TRANSFORM_VIEW); // Force orthogonal with subgizmo. + if (!local_coords) { + new_xform = se->original.affine_inverse() * new_xform; + } + se->gizmo->set_subgizmo_transform(GE.key, new_xform); + } + } else { + Transform3D new_xform = _compute_transform(_edit.mode, se->original, se->original_local, p_motion, p_snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS && _edit.plane != TRANSFORM_VIEW); + _transform_gizmo_apply(se->sp, new_xform, local_coords); + } + } + + spatial_editor->update_transform_gizmo(); + surface->queue_redraw(); +} + // Update the current transform operation in response to an input. void Node3DEditorViewport::update_transform(bool p_shift) { Vector3 ray_pos = _get_ray_pos(_edit.mouse_pos); @@ -4670,43 +4741,11 @@ void Node3DEditorViewport::update_transform(bool p_shift) { set_message(TTR("Scaling:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " + String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")"); if (local_coords) { + // TODO: needed? motion = _edit.original.basis.inverse().xform(motion); } - List<Node *> &selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Node3D *sp = Object::cast_to<Node3D>(E); - if (!sp) { - continue; - } - - Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!se) { - continue; - } - - if (sp->has_meta("_edit_lock_")) { - continue; - } - - if (se->gizmo.is_valid()) { - for (KeyValue<int, Transform3D> &GE : se->subgizmos) { - Transform3D xform = GE.value; - Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original * xform, xform, motion, snap, local_coords, _edit.plane != TRANSFORM_VIEW); // Force orthogonal with subgizmo. - if (!local_coords) { - new_xform = se->original.affine_inverse() * new_xform; - } - se->gizmo->set_subgizmo_transform(GE.key, new_xform); - } - } else { - Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original, se->original_local, motion, snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS && _edit.plane != TRANSFORM_VIEW); - _transform_gizmo_apply(se->sp, new_xform, local_coords); - } - } - - spatial_editor->update_transform_gizmo(); - surface->queue_redraw(); - + apply_transform(motion, snap); } break; case TRANSFORM_TRANSLATE: { @@ -4776,38 +4815,7 @@ void Node3DEditorViewport::update_transform(bool p_shift) { motion = spatial_editor->get_gizmo_transform().basis.inverse().xform(motion); } - List<Node *> &selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Node3D *sp = Object::cast_to<Node3D>(E); - if (!sp) { - continue; - } - - Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!se) { - continue; - } - - if (sp->has_meta("_edit_lock_")) { - continue; - } - - if (se->gizmo.is_valid()) { - for (KeyValue<int, Transform3D> &GE : se->subgizmos) { - Transform3D xform = GE.value; - Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original * xform, xform, motion, snap, local_coords, true); // Force orthogonal with subgizmo. - new_xform = se->original.affine_inverse() * new_xform; - se->gizmo->set_subgizmo_transform(GE.key, new_xform); - } - } else { - Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original, se->original_local, motion, snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS); - _transform_gizmo_apply(se->sp, new_xform, false); - } - } - - spatial_editor->update_transform_gizmo(); - surface->queue_redraw(); - + apply_transform(motion, snap); } break; case TRANSFORM_ROTATE: { @@ -4876,53 +4884,85 @@ void Node3DEditorViewport::update_transform(bool p_shift) { bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); // Disable local transformation for TRANSFORM_VIEW - List<Node *> &selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Node3D *sp = Object::cast_to<Node3D>(E); - if (!sp) { - continue; - } - - Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!se) { - continue; - } - - if (sp->has_meta("_edit_lock_")) { - continue; - } - - Vector3 compute_axis = local_coords ? local_axis : global_axis; - if (se->gizmo.is_valid()) { - for (KeyValue<int, Transform3D> &GE : se->subgizmos) { - Transform3D xform = GE.value; - - Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original * xform, xform, compute_axis, angle, local_coords, true); // Force orthogonal with subgizmo. - if (!local_coords) { - new_xform = se->original.affine_inverse() * new_xform; - } - se->gizmo->set_subgizmo_transform(GE.key, new_xform); - } - } else { - Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original, se->original_local, compute_axis, angle, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS); - _transform_gizmo_apply(se->sp, new_xform, local_coords); - } - } - - spatial_editor->update_transform_gizmo(); - surface->queue_redraw(); - + Vector3 compute_axis = local_coords ? local_axis : global_axis; + apply_transform(compute_axis, angle); } break; default: { } } } -// Perform cleanup after a transform operation is committed or canceled. +void Node3DEditorViewport::update_transform_numeric() { + Vector3 motion; + switch (_edit.plane) { + case TRANSFORM_VIEW: { + switch (_edit.mode) { + case TRANSFORM_TRANSLATE: + motion = Vector3(1, 0, 0); + break; + case TRANSFORM_ROTATE: + motion = spatial_editor->get_gizmo_transform().basis.xform_inv(_get_camera_normal()).normalized(); + break; + case TRANSFORM_SCALE: + motion = Vector3(1, 1, 1); + break; + case TRANSFORM_NONE: + ERR_FAIL_MSG("_edit.mode cannot be TRANSFORM_NONE in update_transform_numeric."); + } + break; + } + case TRANSFORM_X_AXIS: + motion = Vector3(1, 0, 0); + break; + case TRANSFORM_Y_AXIS: + motion = Vector3(0, 1, 0); + break; + case TRANSFORM_Z_AXIS: + motion = Vector3(0, 0, 1); + break; + case TRANSFORM_XY: + motion = Vector3(1, 1, 0); + break; + case TRANSFORM_XZ: + motion = Vector3(1, 0, 1); + break; + case TRANSFORM_YZ: + motion = Vector3(0, 1, 1); + break; + } + + double value = _edit.numeric_input * (_edit.numeric_negate ? -1 : 1); + double extra = 0.0; + switch (_edit.mode) { + case TRANSFORM_TRANSLATE: + motion *= value; + set_message(vformat(TTR("Translating %s."), motion)); + break; + case TRANSFORM_ROTATE: + extra = Math::deg_to_rad(value); + set_message(vformat(TTR("Rotating %f degrees."), value)); + break; + case TRANSFORM_SCALE: + // To halve the size of an object in Blender, you scale it by 0.5. + // Doing the same in Godot is considered scaling it by -0.5. + motion *= (value - 1.0); + set_message(vformat(TTR("Scaling %s."), motion)); + break; + case TRANSFORM_NONE: + ERR_FAIL_MSG("_edit.mode cannot be TRANSFORM_NONE in update_transform_numeric."); + } + + apply_transform(motion, extra); +} + +// Perform cleanup after a transform operation is committed or cancelled. void Node3DEditorViewport::finish_transform() { - spatial_editor->set_local_coords_enabled(_edit.original_local); _edit.mode = TRANSFORM_NONE; _edit.instant = false; + _edit.numeric_input = 0; + _edit.numeric_next_decimal = 0; + _edit.numeric_negate = false; + spatial_editor->set_local_coords_enabled(_edit.original_local); spatial_editor->update_transform_gizmo(); surface->queue_redraw(); set_process_input(false); diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 79674bdd64..e58e224ff4 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -349,6 +349,15 @@ private: Variant gizmo_initial_value; bool original_local; bool instant; + + // Numeric blender-style transforms (e.g. 'g5x'). + // numeric_input tracks the current input value, e.g. 1.23. + // numeric_negate indicates whether '-' has been pressed to negate the value + // while numeric_next_decimal is 0, numbers are input before the decimal point + // after pressing '.', numeric next decimal changes to -1, and decrements after each press. + double numeric_input = 0.0; + bool numeric_negate = false; + int numeric_next_decimal = 0; } _edit; struct Cursor { @@ -445,7 +454,9 @@ private: void begin_transform(TransformMode p_mode, bool instant); void commit_transform(); + void apply_transform(Vector3 p_motion, double p_snap); void update_transform(bool p_shift); + void update_transform_numeric(); void finish_transform(); void register_shortcut_action(const String &p_path, const String &p_name, Key p_keycode, bool p_physical = false); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index c98d9086d1..b1111be006 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -2655,6 +2655,12 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { TileSetAtlasSourceEditor::~TileSetAtlasSourceEditor() { memdelete(tile_proxy_object); memdelete(atlas_source_proxy_object); + + // Remove listener for old objects, so the TileSet doesn't + // try to call the destroyed TileSetAtlasSourceEditor. + if (tile_set.is_valid()) { + tile_set->disconnect_changed(callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); + } } ////// EditorPropertyTilePolygon ////// diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp index 911316822a..e432704702 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -38,7 +38,6 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" -#include "editor/inspector_dock.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "scene/2d/tile_map.h" @@ -326,8 +325,18 @@ void TileMapEditorPlugin::_tile_map_changed() { } void TileMapEditorPlugin::_update_tile_map() { - if (tile_map && tile_map->get_tileset().is_valid()) { - EditorNode::get_singleton()->edit_item(tile_map->get_tileset().ptr(), InspectorDock::get_inspector_singleton()); + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (tile_map) { + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (tile_set.is_valid() && edited_tileset != tile_set->get_instance_id()) { + tile_set_plugin_singleton->edit(tile_map->get_tileset().ptr()); + tile_set_plugin_singleton->make_visible(true); + edited_tileset = tile_set->get_instance_id(); + } else if (tile_set.is_null()) { + tile_set_plugin_singleton->edit(nullptr); + tile_set_plugin_singleton->make_visible(false); + edited_tileset = ObjectID(); + } } tile_map_changed_needs_update = false; } @@ -339,18 +348,26 @@ void TileMapEditorPlugin::_notification(int p_notification) { } void TileMapEditorPlugin::edit(Object *p_object) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (tile_map) { tile_map->disconnect("changed", callable_mp(this, &TileMapEditorPlugin::_tile_map_changed)); } tile_map = Object::cast_to<TileMap>(p_object); + if (tile_map) { + tile_map_id = tile_map->get_instance_id(); + } else { + tile_map_id = ObjectID(); + } editor->edit(tile_map); if (tile_map) { tile_map->connect("changed", callable_mp(this, &TileMapEditorPlugin::_tile_map_changed)); if (tile_map->get_tileset().is_valid()) { - EditorNode::get_singleton()->edit_item(tile_map->get_tileset().ptr(), InspectorDock::get_inspector_singleton()); + tile_set_plugin_singleton->edit(tile_map->get_tileset().ptr()); + tile_set_plugin_singleton->make_visible(true); + edited_tileset = tile_map->get_tileset()->get_instance_id(); } } } diff --git a/editor/plugins/tiles/tiles_editor_plugin.h b/editor/plugins/tiles/tiles_editor_plugin.h index 0bb45b746d..81cb48eb00 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.h +++ b/editor/plugins/tiles/tiles_editor_plugin.h @@ -115,9 +115,11 @@ class TileMapEditorPlugin : public EditorPlugin { TileMapEditor *editor = nullptr; Button *button = nullptr; - TileMap *tile_map = nullptr; + ObjectID tile_map_id; bool tile_map_changed_needs_update = false; + ObjectID edited_tileset; + void _tile_map_changed(); void _update_tile_map(); diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index fbb724906b..849a6cc097 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -275,6 +275,7 @@ void register_editor_types() { GLOBAL_DEF("editor/version_control/autoload_on_startup", false); EditorInterface::create(); + Engine::get_singleton()->add_singleton(Engine::Singleton("EditorInterface", EditorInterface::get_singleton())); OS::get_singleton()->benchmark_end_measure("register_editor_types"); } diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index e8db6ed679..3096d20c19 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -264,6 +264,9 @@ void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, N } void SceneTreeDock::_replace_with_branch_scene(const String &p_file, Node *base) { + // `move_child` + `get_index` doesn't really work for internal nodes. + ERR_FAIL_COND_MSG(base->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to replace internal node, this is not supported."); + Ref<PackedScene> sdata = ResourceLoader::load(p_file); if (!sdata.is_valid()) { accept->set_text(vformat(TTR("Error loading scene from %s"), p_file)); @@ -284,7 +287,7 @@ void SceneTreeDock::_replace_with_branch_scene(const String &p_file, Node *base) undo_redo->create_action(TTR("Replace with Branch Scene")); Node *parent = base->get_parent(); - int pos = base->get_index(); + int pos = base->get_index(false); undo_redo->add_do_method(parent, "remove_child", base); undo_redo->add_undo_method(parent, "remove_child", instantiated_scene); undo_redo->add_do_method(parent, "add_child", instantiated_scene, true); @@ -612,10 +615,12 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { selection.reverse(); } - int lowest_id = common_parent->get_child_count() - 1; + int lowest_id = common_parent->get_child_count(false) - 1; int highest_id = 0; for (Node *E : selection) { - int index = E->get_index(); + // `move_child` + `get_index` doesn't really work for internal nodes. + ERR_FAIL_COND_MSG(E->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to move internal node, this is not supported."); + int index = E->get_index(false); if (index > highest_id) { highest_id = index; @@ -629,7 +634,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { } } - if (!common_parent || (MOVING_DOWN && highest_id >= common_parent->get_child_count() - MOVING_DOWN) || (MOVING_UP && lowest_id == 0)) { + if (!common_parent || (MOVING_DOWN && highest_id >= common_parent->get_child_count(false) - MOVING_DOWN) || (MOVING_UP && lowest_id == 0)) { break; // one or more nodes can not be moved } @@ -648,8 +653,8 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { ERR_FAIL_COND(!top_node->get_parent()); ERR_FAIL_COND(!bottom_node->get_parent()); - int bottom_node_pos = bottom_node->get_index(); - int top_node_pos_next = top_node->get_index() + (MOVING_DOWN ? 1 : -1); + int bottom_node_pos = bottom_node->get_index(false); + int top_node_pos_next = top_node->get_index(false) + (MOVING_DOWN ? 1 : -1); undo_redo->add_do_method(top_node->get_parent(), "move_child", top_node, top_node_pos_next); undo_redo->add_undo_method(bottom_node->get_parent(), "move_child", bottom_node, bottom_node_pos); @@ -780,6 +785,9 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { return; } + // `move_child` + `get_index` doesn't really work for internal nodes. + ERR_FAIL_COND_MSG(node->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to set internal node as scene root, this is not supported."); + //check that from node to root, all owners are right if (root->get_scene_inherited_state().is_valid()) { @@ -816,7 +824,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { undo_redo->add_undo_method(node, "remove_child", root); undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", root); undo_redo->add_undo_method(node->get_parent(), "add_child", node, true); - undo_redo->add_undo_method(node->get_parent(), "move_child", node, node->get_index()); + undo_redo->add_undo_method(node->get_parent(), "move_child", node, node->get_index(false)); undo_redo->add_undo_method(root, "set_owner", (Object *)nullptr); undo_redo->add_undo_method(node, "set_owner", root); _node_replace_owner(root, root, root, MODE_UNDO); @@ -1908,8 +1916,10 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V if (p_nodes[ni] == p_new_parent) { return; // Attempt to reparent to itself. } + // `move_child` + `get_index` doesn't really work for internal nodes. + ERR_FAIL_COND_MSG(p_nodes[ni]->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to move internal node, this is not supported."); - if (p_nodes[ni]->get_parent() != p_new_parent || p_position_in_parent + ni != p_nodes[ni]->get_index()) { + if (p_nodes[ni]->get_parent() != p_new_parent || p_position_in_parent + ni != p_nodes[ni]->get_index(false)) { no_change = false; } } @@ -1950,7 +1960,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V } bool same_parent = new_parent == node->get_parent(); - if (same_parent && node->get_index() < p_position_in_parent + ni) { + if (same_parent && node->get_index(false) < p_position_in_parent + ni) { inc--; // If the child will generate a gap when moved, adjust. } @@ -1992,7 +2002,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V } undo_redo->add_do_method(ed, "live_debug_reparent_node", edited_scene->get_path_to(node), edited_scene->get_path_to(new_parent), new_name, new_position_in_parent); - undo_redo->add_undo_method(ed, "live_debug_reparent_node", NodePath(String(edited_scene->get_path_to(new_parent)).path_join(new_name)), edited_scene->get_path_to(node->get_parent()), node->get_name(), node->get_index()); + undo_redo->add_undo_method(ed, "live_debug_reparent_node", NodePath(String(edited_scene->get_path_to(new_parent)).path_join(new_name)), edited_scene->get_path_to(node->get_parent()), node->get_name(), node->get_index(false)); if (p_keep_global_xform) { if (Object::cast_to<Node2D>(node)) { @@ -2029,7 +2039,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V owners.push_back(E); } - int child_pos = node->get_index(); + int child_pos = node->get_index(false); undo_redo->add_undo_method(node->get_parent(), "add_child", node, true); undo_redo->add_undo_method(node->get_parent(), "move_child", node, child_pos); @@ -2177,6 +2187,10 @@ void SceneTreeDock::_delete_confirm(bool p_cut) { undo_redo->add_undo_method(scene_tree, "update_tree"); undo_redo->add_undo_reference(edited_scene); } else { + for (const Node *E : remove_list) { + // `move_child` + `get_index` doesn't really work for internal nodes. + ERR_FAIL_COND_MSG(E->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to remove internal node, this is not supported."); + } if (delete_tracks_checkbox->is_pressed() || p_cut) { remove_list.sort_custom<Node::Comparator>(); // Sort nodes to keep positions. HashMap<Node *, NodePath> path_renames; @@ -2208,7 +2222,7 @@ void SceneTreeDock::_delete_confirm(bool p_cut) { undo_redo->add_do_method(n->get_parent(), "remove_child", n); undo_redo->add_undo_method(n->get_parent(), "add_child", n, true); - undo_redo->add_undo_method(n->get_parent(), "move_child", n, n->get_index()); + undo_redo->add_undo_method(n->get_parent(), "move_child", n, n->get_index(false)); if (AnimationPlayerEditor::get_singleton()->get_track_editor()->get_root() == n) { undo_redo->add_undo_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", n); } @@ -2217,7 +2231,7 @@ void SceneTreeDock::_delete_confirm(bool p_cut) { EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); undo_redo->add_do_method(ed, "live_debug_remove_and_keep_node", edited_scene->get_path_to(n), n->get_instance_id()); - undo_redo->add_undo_method(ed, "live_debug_restore_node", n->get_instance_id(), edited_scene->get_path_to(n->get_parent()), n->get_index()); + undo_redo->add_undo_method(ed, "live_debug_restore_node", n->get_instance_id(), edited_scene->get_path_to(n->get_parent()), n->get_index(false)); } } undo_redo->commit_action(); @@ -2710,7 +2724,7 @@ void SceneTreeDock::_normalize_drop(Node *&to_node, int &to_pos, int p_type) { ERR_FAIL_MSG("Cannot perform drop above the root node!"); } - to_pos = to_node->get_index(); + to_pos = to_node->get_index(false); to_node = to_node->get_parent(); } else if (p_type == 1) { @@ -2726,15 +2740,15 @@ void SceneTreeDock::_normalize_drop(Node *&to_node, int &to_pos, int p_type) { if (_has_visible_children(to_node)) { to_pos = 0; } else { - for (int i = to_node->get_index() + 1; i < to_node->get_parent()->get_child_count(); i++) { - Node *c = to_node->get_parent()->get_child(i); + for (int i = to_node->get_index(false) + 1; i < to_node->get_parent()->get_child_count(false); i++) { + Node *c = to_node->get_parent()->get_child(i, false); if (_is_node_visible(c)) { lower_sibling = c; break; } } if (lower_sibling) { - to_pos = lower_sibling->get_index(); + to_pos = lower_sibling->get_index(false); } to_node = to_node->get_parent(); @@ -3771,16 +3785,16 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec ED_SHORTCUT("scene_tree/batch_rename", TTR("Batch Rename"), KeyModifierMask::SHIFT | Key::F2); ED_SHORTCUT_OVERRIDE("scene_tree/batch_rename", "macos", KeyModifierMask::SHIFT | Key::ENTER); - ED_SHORTCUT("scene_tree/add_child_node", TTR("Add Child Node"), KeyModifierMask::CMD_OR_CTRL | Key::A); - ED_SHORTCUT("scene_tree/instantiate_scene", TTR("Instantiate Child Scene"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::A); + ED_SHORTCUT("scene_tree/add_child_node", TTR("Add Child Node..."), KeyModifierMask::CMD_OR_CTRL | Key::A); + ED_SHORTCUT("scene_tree/instantiate_scene", TTR("Instantiate Child Scene..."), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::A); ED_SHORTCUT("scene_tree/expand_collapse_all", TTR("Expand/Collapse Branch")); ED_SHORTCUT("scene_tree/cut_node", TTR("Cut"), KeyModifierMask::CMD_OR_CTRL | Key::X); ED_SHORTCUT("scene_tree/copy_node", TTR("Copy"), KeyModifierMask::CMD_OR_CTRL | Key::C); ED_SHORTCUT("scene_tree/paste_node", TTR("Paste"), KeyModifierMask::CMD_OR_CTRL | Key::V); ED_SHORTCUT("scene_tree/paste_node_as_sibling", TTR("Paste as Sibling"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::V); - ED_SHORTCUT("scene_tree/change_node_type", TTR("Change Type")); - ED_SHORTCUT("scene_tree/attach_script", TTR("Attach Script")); - ED_SHORTCUT("scene_tree/extend_script", TTR("Extend Script")); + ED_SHORTCUT("scene_tree/change_node_type", TTR("Change Type...")); + ED_SHORTCUT("scene_tree/attach_script", TTR("Attach Script...")); + ED_SHORTCUT("scene_tree/extend_script", TTR("Extend Script...")); ED_SHORTCUT("scene_tree/detach_script", TTR("Detach Script")); ED_SHORTCUT("scene_tree/move_up", TTR("Move Up"), KeyModifierMask::CMD_OR_CTRL | Key::UP); ED_SHORTCUT("scene_tree/move_down", TTR("Move Down"), KeyModifierMask::CMD_OR_CTRL | Key::DOWN); diff --git a/main/main.cpp b/main/main.cpp index 4bb5e7bf13..e6dd576b8a 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -458,6 +458,7 @@ void Main::print_help(const char *p_binary) { #if DEBUG_ENABLED OS::get_singleton()->print(" --gpu-abort Abort on graphics API usage errors (usually validation layer errors). May help see the problem if your system freezes.\n"); #endif + OS::get_singleton()->print(" --generate-spirv-debug-info Generate SPIR-V debug information. This allows source-level shader debugging with RenderDoc.\n"); OS::get_singleton()->print(" --remote-debug <uri> Remote debug (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007).\n"); OS::get_singleton()->print(" --single-threaded-scene Scene tree runs in single-threaded mode. Sub-thread groups are disabled and run on the main thread.\n"); #if defined(DEBUG_ENABLED) @@ -1019,6 +1020,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph } else if (I->get() == "--gpu-abort") { Engine::singleton->abort_on_gpu_errors = true; #endif + } else if (I->get() == "--generate-spirv-debug-info") { + Engine::singleton->generate_spirv_debug_info = true; } else if (I->get() == "--tablet-driver") { if (I->next()) { tablet_driver = I->next()->get(); @@ -1540,6 +1543,12 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph } } +#ifdef TOOLS_ENABLED + if (editor) { + Engine::get_singleton()->set_editor_hint(true); + } +#endif + // Initialize user data dir. OS::get_singleton()->ensure_user_data_dir(); @@ -1567,7 +1576,6 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph #ifdef TOOLS_ENABLED if (editor) { packed_data->set_disabled(true); - Engine::get_singleton()->set_editor_hint(true); main_args.push_back("--editor"); if (!init_windowed) { init_maximized = true; @@ -3122,6 +3130,7 @@ bool Main::start() { Size2i stretch_size = Size2i(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); real_t stretch_scale = GLOBAL_GET("display/window/stretch/scale"); + String stretch_scale_mode = GLOBAL_GET("display/window/stretch/scale_mode"); Window::ContentScaleMode cs_sm = Window::CONTENT_SCALE_MODE_DISABLED; if (stretch_mode == "canvas_items") { @@ -3141,8 +3150,14 @@ bool Main::start() { cs_aspect = Window::CONTENT_SCALE_ASPECT_EXPAND; } + Window::ContentScaleStretch cs_stretch = Window::CONTENT_SCALE_STRETCH_FRACTIONAL; + if (stretch_scale_mode == "integer") { + cs_stretch = Window::CONTENT_SCALE_STRETCH_INTEGER; + } + sml->get_root()->set_content_scale_mode(cs_sm); sml->get_root()->set_content_scale_aspect(cs_aspect); + sml->get_root()->set_content_scale_stretch(cs_stretch); sml->get_root()->set_content_scale_size(stretch_size); sml->get_root()->set_content_scale_factor(stretch_scale); diff --git a/methods.py b/methods.py index 571a3f739e..7c1781d699 100644 --- a/methods.py +++ b/methods.py @@ -868,6 +868,9 @@ def generate_vs_project(env, num_jobs, project_name="godot"): if env["custom_modules"]: common_build_postfix.append("custom_modules=%s" % env["custom_modules"]) + if env["windows_subsystem"] == "console": + common_build_postfix.append("windows_subsystem=console") + if env["precision"] == "double": common_build_postfix.append("precision=double") diff --git a/misc/scripts/validate_extension_api.sh b/misc/scripts/validate_extension_api.sh index e06d52115a..f2f7c28e70 100755 --- a/misc/scripts/validate_extension_api.sh +++ b/misc/scripts/validate_extension_api.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -uo pipefail +set -o pipefail if [ ! -f "version.py" ]; then echo "Warning: This script is intended to be run from the root of the Godot repository." @@ -18,7 +18,7 @@ make_annotation() local body=$2 local type=$3 local file=$4 - if [ ! -v GITHUB_OUTPUT ]; then + if [[ "$GITHUB_OUTPUT" == "" ]]; then echo "$title" echo "$body" else @@ -43,8 +43,8 @@ while read -r file; do awk '/^Validate extension JSON:/' - < "$file" | sort > "$allowed_errors" # Differences between the expected and actual errors - new_validation_error="$(comm "$validation_output" "$allowed_errors" -23)" - obsolete_validation_error="$(comm "$validation_output" "$allowed_errors" -13)" + new_validation_error="$(comm -23 "$validation_output" "$allowed_errors")" + obsolete_validation_error="$(comm -13 "$validation_output" "$allowed_errors")" if [ -n "$obsolete_validation_error" ]; then make_annotation "The following validation errors no longer occur (compared to $reference_tag):" "$obsolete_validation_error" warning "$file" diff --git a/modules/csg/doc_classes/CSGMesh3D.xml b/modules/csg/doc_classes/CSGMesh3D.xml index 9a0f121e19..96d89ff486 100644 --- a/modules/csg/doc_classes/CSGMesh3D.xml +++ b/modules/csg/doc_classes/CSGMesh3D.xml @@ -16,7 +16,8 @@ </member> <member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh"> The [Mesh] resource to use as a CSG shape. - [b]Note:[/b] When using an [ArrayMesh], avoid meshes with vertex normals unless a flat shader is required. By default, CSGMesh will ignore the mesh's vertex normals and use a smooth shader calculated using the faces' normals. If a flat shader is required, ensure that all faces' vertex normals are parallel. + [b]Note:[/b] When using an [ArrayMesh], all vertex attributes except [constant Mesh.ARRAY_VERTEX], [constant Mesh.ARRAY_NORMAL] and [constant Mesh.ARRAY_TEX_UV] are left unused. Only [constant Mesh.ARRAY_VERTEX] and [constant Mesh.ARRAY_TEX_UV] will be passed to the GPU. + [constant Mesh.ARRAY_NORMAL] is only used to determine which faces require the use of flat shading. By default, CSGMesh will ignore the mesh's vertex normals, recalculate them for each vertex and use a smooth shader. If a flat shader is required for a face, ensure that all vertex normals of the face are approximately equal. </member> </members> </class> diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index cb04913620..ad4528747b 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -3469,6 +3469,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod for (GDScriptParser::ClassNode *script_class : script_classes) { if (p_base == nullptr && script_class->identifier && script_class->identifier->name == name) { reduce_identifier_from_base_set_class(p_identifier, script_class->get_datatype()); + if (script_class->outer != nullptr) { + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CLASS; + } return; } diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp index 9e14e43a58..3b89f077bd 100644 --- a/modules/gdscript/gdscript_lambda_callable.cpp +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -67,6 +67,10 @@ ObjectID GDScriptLambdaCallable::get_object() const { return script->get_instance_id(); } +StringName GDScriptLambdaCallable::get_method() const { + return function->get_name(); +} + void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { int captures_amount = captures.size(); diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h index 33bdf6dfc1..1c7a18fb9d 100644 --- a/modules/gdscript/gdscript_lambda_callable.h +++ b/modules/gdscript/gdscript_lambda_callable.h @@ -56,6 +56,7 @@ public: CompareEqualFunc get_compare_equal_func() const override; CompareLessFunc get_compare_less_func() const override; ObjectID get_object() const override; + StringName get_method() const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index debc85ebbf..9e1e8f0c75 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -2446,7 +2446,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(Expression complete_extents(operation); if (operation->right_operand == nullptr) { - push_error(vformat(R"(Expected expression after "%s" operator.")", op.get_name())); + push_error(vformat(R"(Expected expression after "%s" operator.)", op.get_name())); } // TODO: Also for unary, ternary, and assignment. diff --git a/modules/gdscript/gdscript_rpc_callable.cpp b/modules/gdscript/gdscript_rpc_callable.cpp index a4dd8a8d3c..265e624b6c 100644 --- a/modules/gdscript/gdscript_rpc_callable.cpp +++ b/modules/gdscript/gdscript_rpc_callable.cpp @@ -63,6 +63,10 @@ ObjectID GDScriptRPCCallable::get_object() const { return object->get_instance_id(); } +StringName GDScriptRPCCallable::get_method() const { + return method; +} + void GDScriptRPCCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { r_return_value = object->callp(method, p_arguments, p_argcount, r_call_error); } diff --git a/modules/gdscript/gdscript_rpc_callable.h b/modules/gdscript/gdscript_rpc_callable.h index c1007b18b0..66052157be 100644 --- a/modules/gdscript/gdscript_rpc_callable.h +++ b/modules/gdscript/gdscript_rpc_callable.h @@ -51,6 +51,7 @@ public: CompareEqualFunc get_compare_equal_func() const override; CompareLessFunc get_compare_less_func() const override; ObjectID get_object() const override; + StringName get_method() const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const override; diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.gd b/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.gd new file mode 100644 index 0000000000..51c589b8e0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.gd @@ -0,0 +1,21 @@ +# GH-80508 + +class A: + func a(): + return A.new() + func b(): + return B.new() + +class B: + func a(): + return A.new() + func b(): + return B.new() + +func test(): + var a := A.new() + var b := B.new() + print(a.a() is A) + print(a.b() is B) + print(b.a() is A) + print(b.b() is B) diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.out b/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.out new file mode 100644 index 0000000000..f9783e4362 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inner_class_access_from_inside.out @@ -0,0 +1,5 @@ +GDTEST_OK +true +true +true +true diff --git a/modules/glslang/register_types.cpp b/modules/glslang/register_types.cpp index 622910761d..2b070c24b8 100644 --- a/modules/glslang/register_types.cpp +++ b/modules/glslang/register_types.cpp @@ -32,6 +32,7 @@ #include "glslang_resource_limits.h" +#include "core/config/engine.h" #include "servers/rendering/rendering_device.h" #include <glslang/Include/Types.h> @@ -56,7 +57,6 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage glslang::EShTargetClientVersion ClientVersion = glslang::EShTargetVulkan_1_2; glslang::EShTargetLanguageVersion TargetVersion = glslang::EShTargetSpv_1_5; - glslang::TShader::ForbidIncluder includer; if (capabilities->device_family == RenderingDevice::DeviceFamily::DEVICE_VULKAN) { if (capabilities->version_major == 1 && capabilities->version_minor == 0) { @@ -127,23 +127,10 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage } EShMessages messages = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules); - const int DefaultVersion = 100; - std::string pre_processed_code; - - //preprocess - if (!shader.preprocess(&DefaultTBuiltInResource, DefaultVersion, ENoProfile, false, false, messages, &pre_processed_code, includer)) { - if (r_error) { - (*r_error) = "Failed pre-process:\n"; - (*r_error) += shader.getInfoLog(); - (*r_error) += "\n"; - (*r_error) += shader.getInfoDebugLog(); - } - - return ret; + if (Engine::get_singleton()->is_generate_spirv_debug_info_enabled()) { + messages = (EShMessages)(messages | EShMsgDebugInfo); } - //set back.. - cs_strings = pre_processed_code.c_str(); - shader.setStrings(&cs_strings, 1); + const int DefaultVersion = 100; //parse if (!shader.parse(&DefaultTBuiltInResource, DefaultVersion, false, messages)) { @@ -174,6 +161,13 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage std::vector<uint32_t> SpirV; spv::SpvBuildLogger logger; glslang::SpvOptions spvOptions; + + if (Engine::get_singleton()->is_generate_spirv_debug_info_enabled()) { + spvOptions.generateDebugInfo = true; + spvOptions.emitNonSemanticShaderDebugInfo = true; + spvOptions.emitNonSemanticShaderDebugSource = true; + } + glslang::GlslangToSpv(*program.getIntermediate(stages[p_stage]), SpirV, &logger, &spvOptions); ret.resize(SpirV.size() * sizeof(uint32_t)); @@ -188,7 +182,7 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage static String _get_cache_key_function_glsl(const RenderingDevice *p_render_device) { const RD::Capabilities *capabilities = p_render_device->get_device_capabilities(); String version; - version = "SpirVGen=" + itos(glslang::GetSpirvGeneratorVersion()) + ", major=" + itos(capabilities->version_major) + ", minor=" + itos(capabilities->version_minor) + " , subgroup_size=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_SIZE)) + " , subgroup_ops=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_OPERATIONS)) + " , subgroup_in_shaders=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_IN_SHADERS)); + version = "SpirVGen=" + itos(glslang::GetSpirvGeneratorVersion()) + ", major=" + itos(capabilities->version_major) + ", minor=" + itos(capabilities->version_minor) + " , subgroup_size=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_SIZE)) + " , subgroup_ops=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_OPERATIONS)) + " , subgroup_in_shaders=" + itos(p_render_device->limit_get(RD::LIMIT_SUBGROUP_IN_SHADERS)) + " , debug=" + itos(Engine::get_singleton()->is_generate_spirv_debug_info_enabled()); return version; } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs index d7d484d166..8a292fd73a 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -23,7 +23,7 @@ namespace GodotTools.Build if (dotnetPath == null) throw new FileNotFoundException("Cannot find the dotnet executable."); - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); var startInfo = new ProcessStartInfo(dotnetPath); @@ -94,7 +94,7 @@ namespace GodotTools.Build if (dotnetPath == null) throw new FileNotFoundException("Cannot find the dotnet executable."); - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); var startInfo = new ProcessStartInfo(dotnetPath); diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 618d255938..ec28658557 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -473,10 +473,9 @@ namespace GodotTools } } - var editorInterface = GetEditorInterface(); - var editorBaseControl = editorInterface.GetBaseControl(); + var editorBaseControl = EditorInterface.Singleton.GetBaseControl(); - _editorSettings = editorInterface.GetEditorSettings(); + _editorSettings = EditorInterface.Singleton.GetEditorSettings(); _errorDialog = new AcceptDialog(); editorBaseControl.AddChild(_errorDialog); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs index 5d1a2277f9..65b77112aa 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -79,7 +79,7 @@ namespace GodotTools.Ides public async Task<EditorPick?> LaunchIdeAsync(int millisecondsTimeout = 10000) { - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); var editorId = editorSettings.GetSetting(GodotSharpEditor.Settings.ExternalEditor).As<ExternalEditorId>(); string editorIdentity = GetExternalEditorIdentity(editorId); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs index 0d77b8999a..fbbd01dafd 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs @@ -23,7 +23,7 @@ namespace GodotTools.Ides.Rider private static string GetRiderPathFromSettings() { - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); if (editorSettings.HasSetting(EditorPathSettingName)) return (string)editorSettings.GetSetting(EditorPathSettingName); return null; @@ -31,7 +31,7 @@ namespace GodotTools.Ides.Rider public static void Initialize() { - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); var editor = editorSettings.GetSetting(GodotSharpEditor.Settings.ExternalEditor).As<ExternalEditorId>(); if (editor == ExternalEditorId.Rider) { @@ -92,7 +92,7 @@ namespace GodotTools.Ides.Rider string newPath = riderInfos.Length > 0 ? riderInfos[riderInfos.Length - 1].Path : allInfos[allInfos.Length - 1].Path; - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); editorSettings.SetSetting(EditorPathSettingName, newPath); Globals.EditorDef(EditorPathSettingName, newPath); return newPath; diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 1554b89c33..006aca6c73 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -148,7 +148,7 @@ static String fix_doc_description(const String &p_bbcode) { .strip_edges(); } -String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype) { +String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype, bool p_is_signal) { // Based on the version in EditorHelp if (p_bbcode.is_empty()) { @@ -305,11 +305,11 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf _append_xml_enum(xml_output, target_itype, target_cname, link_target, link_target_parts); } else if (link_tag == "constant") { _append_xml_constant(xml_output, target_itype, target_cname, link_target, link_target_parts); + } else if (link_tag == "param") { + _append_xml_param(xml_output, link_target, p_is_signal); } else if (link_tag == "theme_item") { // We do not declare theme_items in any way in C#, so there is nothing to reference _append_xml_undeclared(xml_output, link_target); - } else if (link_tag == "param") { - _append_xml_undeclared(xml_output, snake_to_camel_case(link_target, false)); } pos = brk_end + 1; @@ -684,7 +684,7 @@ void BindingsGenerator::_append_xml_constant(StringBuilder &p_xml_output, const p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); p_xml_output.append(p_target_itype->proxy_name); p_xml_output.append("."); - p_xml_output.append(target_ienum->cname); + p_xml_output.append(target_ienum->proxy_name); p_xml_output.append("."); p_xml_output.append(target_iconst->proxy_name); p_xml_output.append("\"/>"); @@ -725,7 +725,7 @@ void BindingsGenerator::_append_xml_constant_in_global_scope(StringBuilder &p_xm if (target_iconst) { p_xml_output.append("<see cref=\"" BINDINGS_NAMESPACE "."); - p_xml_output.append(target_ienum->cname); + p_xml_output.append(target_ienum->proxy_name); p_xml_output.append("."); p_xml_output.append(target_iconst->proxy_name); p_xml_output.append("\"/>"); @@ -736,6 +736,21 @@ void BindingsGenerator::_append_xml_constant_in_global_scope(StringBuilder &p_xm } } +void BindingsGenerator::_append_xml_param(StringBuilder &p_xml_output, const String &p_link_target, bool p_is_signal) { + const String link_target = snake_to_camel_case(p_link_target); + + if (!p_is_signal) { + p_xml_output.append("<paramref name=\""); + p_xml_output.append(link_target); + p_xml_output.append("\"/>"); + } else { + // Documentation in C# is added to an event, not the delegate itself; + // as such, we treat these parameters as codeblocks instead. + // See: https://github.com/godotengine/godot/pull/65529 + _append_xml_undeclared(p_xml_output, link_target); + } +} + void BindingsGenerator::_append_xml_undeclared(StringBuilder &p_xml_output, const String &p_link_target) { p_xml_output.append("<c>"); p_xml_output.append(p_link_target); @@ -981,7 +996,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { for (const EnumInterface &ienum : global_enums) { CRASH_COND(ienum.constants.is_empty()); - String enum_proxy_name = ienum.cname.operator String(); + String enum_proxy_name = ienum.proxy_name; bool enum_in_static_class = false; @@ -995,7 +1010,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { _log("Declaring global enum '%s' inside struct '%s'\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data()); p_output.append("\npublic partial struct "); - p_output.append(pascal_to_pascal_case(enum_class_name)); + p_output.append(enum_class_name); p_output.append("\n" OPEN_BLOCK); } @@ -1004,7 +1019,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { } p_output.append("\npublic enum "); - p_output.append(pascal_to_pascal_case(enum_proxy_name)); + p_output.append(enum_proxy_name); p_output.append(" : long"); p_output.append("\n" OPEN_BLOCK); @@ -1469,7 +1484,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str } output.append(MEMBER_BEGIN "public enum "); - output.append(pascal_to_pascal_case(ienum.cname.operator String())); + output.append(ienum.proxy_name); output.append(" : long"); output.append(MEMBER_BEGIN OPEN_BLOCK); @@ -2330,31 +2345,31 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf // Generate signal { - p_output.append(MEMBER_BEGIN "/// <summary>\n"); - p_output.append(INDENT1 "/// "); - p_output.append("Represents the method that handles the "); - p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "." + p_isignal.proxy_name + "\"/>"); - p_output.append(" event of a "); - p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "\"/>"); - p_output.append(" class.\n"); - p_output.append(INDENT1 "/// </summary>"); - - if (p_isignal.is_deprecated) { - if (p_isignal.deprecation_message.is_empty()) { - WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'."); - } - - p_output.append(MEMBER_BEGIN "[Obsolete(\""); - p_output.append(p_isignal.deprecation_message); - p_output.append("\")]"); - } - bool is_parameterless = p_isignal.arguments.size() == 0; // Delegate name is [SignalName]EventHandler String delegate_name = is_parameterless ? "Action" : p_isignal.proxy_name + "EventHandler"; if (!is_parameterless) { + p_output.append(MEMBER_BEGIN "/// <summary>\n"); + p_output.append(INDENT1 "/// "); + p_output.append("Represents the method that handles the "); + p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "." + p_isignal.proxy_name + "\"/>"); + p_output.append(" event of a "); + p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "\"/>"); + p_output.append(" class.\n"); + p_output.append(INDENT1 "/// </summary>"); + + if (p_isignal.is_deprecated) { + if (p_isignal.deprecation_message.is_empty()) { + WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'."); + } + + p_output.append(MEMBER_BEGIN "[Obsolete(\""); + p_output.append(p_isignal.deprecation_message); + p_output.append("\")]"); + } + // Generate delegate p_output.append(MEMBER_BEGIN "public delegate void "); p_output.append(delegate_name); @@ -2390,7 +2405,7 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf } if (p_isignal.method_doc && p_isignal.method_doc->description.size()) { - String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype); + String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype, true); Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { @@ -3316,8 +3331,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { enum_proxy_name += "Enum"; enum_proxy_cname = StringName(enum_proxy_name); } - EnumInterface ienum(enum_proxy_cname); - ienum.is_flags = E.value.is_bitfield; + EnumInterface ienum(enum_proxy_cname, enum_proxy_name, E.value.is_bitfield); const List<StringName> &enum_constants = E.value.constants; for (const StringName &constant_cname : enum_constants) { String constant_name = constant_cname.operator String(); @@ -3950,8 +3964,7 @@ void BindingsGenerator::_populate_global_constants() { iconstant.const_doc = const_doc; if (enum_name != StringName()) { - EnumInterface ienum(enum_name); - ienum.is_flags = CoreConstants::is_global_constant_bitfield(i); + EnumInterface ienum(enum_name, pascal_to_pascal_case(enum_name.operator String()), CoreConstants::is_global_constant_bitfield(i)); List<EnumInterface>::Element *enum_match = global_enums.find(ienum); if (enum_match) { enum_match->get().constants.push_back(iconstant); @@ -3969,7 +3982,7 @@ void BindingsGenerator::_populate_global_constants() { enum_itype.is_enum = true; enum_itype.name = ienum.cname.operator String(); enum_itype.cname = ienum.cname; - enum_itype.proxy_name = pascal_to_pascal_case(enum_itype.name); + enum_itype.proxy_name = ienum.proxy_name; TypeInterface::postsetup_enum_type(enum_itype); enum_types.insert(enum_itype.cname, enum_itype); @@ -4014,7 +4027,7 @@ void BindingsGenerator::_initialize_blacklisted_methods() { } void BindingsGenerator::_initialize_compat_singletons() { - // No compat singletons yet. + compat_singletons.insert("EditorInterface"); } void BindingsGenerator::_log(const char *p_format, ...) { diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index d4c7a59e74..6118576bb6 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -60,6 +60,7 @@ class BindingsGenerator { struct EnumInterface { StringName cname; + String proxy_name; List<ConstantInterface> constants; bool is_flags = false; @@ -69,8 +70,10 @@ class BindingsGenerator { EnumInterface() {} - EnumInterface(const StringName &p_cname) { + EnumInterface(const StringName &p_cname, const String &p_proxy_name, bool p_is_flags) { cname = p_cname; + proxy_name = p_proxy_name; + is_flags = p_is_flags; } }; @@ -751,7 +754,7 @@ class BindingsGenerator { return p_type->name; } - String bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype); + String bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype, bool p_is_signal = false); void _append_xml_method(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts); void _append_xml_member(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts); @@ -759,6 +762,7 @@ class BindingsGenerator { void _append_xml_enum(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts); void _append_xml_constant(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts); void _append_xml_constant_in_global_scope(StringBuilder &p_xml_output, const String &p_target_cname, const String &p_link_target); + void _append_xml_param(StringBuilder &p_xml_output, const String &p_link_target, bool p_is_signal); void _append_xml_undeclared(StringBuilder &p_xml_output, const String &p_link_target); int _determine_enum_prefix(const EnumInterface &p_ienum); diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index 00ef4ccdde..c84ecf4ceb 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -33,10 +33,6 @@ #include "mono_gd/gd_mono.h" #include "utils/path_utils.h" -#ifdef ANDROID_ENABLED -#include "mono_gd/support/android_support.h" -#endif - #include "core/config/project_settings.h" #include "core/io/dir_access.h" #include "core/os/os.h" diff --git a/modules/noise/doc_classes/FastNoiseLite.xml b/modules/noise/doc_classes/FastNoiseLite.xml index 4c6cdfbf12..1ab2552547 100644 --- a/modules/noise/doc_classes/FastNoiseLite.xml +++ b/modules/noise/doc_classes/FastNoiseLite.xml @@ -5,7 +5,7 @@ </brief_description> <description> This class generates noise using the FastNoiseLite library, which is a collection of several noise algorithms including Cellular, Perlin, Value, and more. - Most generated noise values are in the range of [code][-1,1][/code], however not always. Some of the cellular noise algorithms return results above [code]1[/code]. + Most generated noise values are in the range of [code][-1, 1][/code], but not always. Some of the cellular noise algorithms return results above [code]1[/code]. </description> <tutorials> </tutorials> diff --git a/modules/noise/doc_classes/Noise.xml b/modules/noise/doc_classes/Noise.xml index dd232af1cc..7d74c84f93 100644 --- a/modules/noise/doc_classes/Noise.xml +++ b/modules/noise/doc_classes/Noise.xml @@ -5,7 +5,7 @@ </brief_description> <description> This class defines the interface for noise generation libraries to inherit from. - A default get_seamless_noise() implementation is provided for libraries that do not provide seamless noise. This function requests a larger image from get_image(), reverses the quadrants of the image, then uses the strips of extra width to blend over the seams. + A default [method get_seamless_image] implementation is provided for libraries that do not provide seamless noise. This function requests a larger image from the [method get_image] method, reverses the quadrants of the image, then uses the strips of extra width to blend over the seams. Inheriting noise classes can optionally override this function to provide a more optimal algorithm. </description> <tutorials> diff --git a/modules/noise/doc_classes/NoiseTexture2D.xml b/modules/noise/doc_classes/NoiseTexture2D.xml index e25af794d4..3f4afc5141 100644 --- a/modules/noise/doc_classes/NoiseTexture2D.xml +++ b/modules/noise/doc_classes/NoiseTexture2D.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="NoiseTexture2D" inherits="Texture2D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - A texture filled with noise generated by a [Noise] object. + A 2D texture filled with noise generated by a [Noise] object. </brief_description> <description> - Uses [FastNoiseLite] or other libraries to fill the texture data of your desired size. [NoiseTexture2D] can also generate normal map textures. + Uses the [FastNoiseLite] library or other noise generators to fill the texture data of your desired size. [NoiseTexture2D] can also generate normal map textures. The class uses [Thread]s to generate the texture data internally, so [method Texture2D.get_image] may return [code]null[/code] if the generation process has not completed yet. In that case, you need to wait for the texture to be generated before accessing the image and the generated byte data: [codeblock] var texture = NoiseTexture2D.new() diff --git a/modules/noise/doc_classes/NoiseTexture3D.xml b/modules/noise/doc_classes/NoiseTexture3D.xml index 0ada6942ad..e8e205bc68 100644 --- a/modules/noise/doc_classes/NoiseTexture3D.xml +++ b/modules/noise/doc_classes/NoiseTexture3D.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="NoiseTexture3D" inherits="Texture3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - A texture filled with noise generated by a [Noise] object. + A 3D texture filled with noise generated by a [Noise] object. </brief_description> <description> - Uses [FastNoiseLite] or other libraries to fill the texture data of your desired size. + Uses the [FastNoiseLite] library or other noise generators to fill the texture data of your desired size. The class uses [Thread]s to generate the texture data internally, so [method Texture3D.get_data] may return [code]null[/code] if the generation process has not completed yet. In that case, you need to wait for the texture to be generated before accessing the image: [codeblock] var texture = NoiseTexture3D.new() diff --git a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp index 81ba9c56b8..4829f713d2 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp +++ b/modules/openxr/extensions/openxr_extension_wrapper_extension.cpp @@ -126,7 +126,7 @@ void OpenXRExtensionWrapperExtension::on_before_instance_created() { } void OpenXRExtensionWrapperExtension::on_instance_created(const XrInstance p_instance) { - uint64_t instance = reinterpret_cast<uint64_t>(p_instance); + uint64_t instance = (uint64_t)p_instance; GDVIRTUAL_CALL(_on_instance_created, instance); } @@ -135,7 +135,7 @@ void OpenXRExtensionWrapperExtension::on_instance_destroyed() { } void OpenXRExtensionWrapperExtension::on_session_created(const XrSession p_session) { - uint64_t session = reinterpret_cast<uint64_t>(p_session); + uint64_t session = (uint64_t)p_session; GDVIRTUAL_CALL(_on_session_created, session); } diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 043a33ab35..2a502d081e 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -2113,12 +2113,10 @@ Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid) name = vformat("unknown_%d", names[i].name_id); } break; } + String text; unsigned int text_size = hb_ot_name_get_utf32(hb_face, names[i].name_id, names[i].language, nullptr, nullptr) + 1; - // @todo After godot-cpp#1141 is fixed, use text.resize() and write directly to text.wptr() instead of using a temporary buffer. - char32_t *buffer = memnew_arr(char32_t, text_size); - hb_ot_name_get_utf32(hb_face, names[i].name_id, names[i].language, &text_size, (uint32_t *)buffer); - String text(buffer); - memdelete_arr(buffer); + text.resize(text_size); + hb_ot_name_get_utf32(hb_face, names[i].name_id, names[i].language, &text_size, (uint32_t *)text.ptrw()); if (!text.is_empty()) { Dictionary &id_string = names_for_lang[String(hb_language_to_string(names[i].language))]; id_string[name] = text; diff --git a/modules/text_server_adv/thorvg_svg_in_ot.cpp b/modules/text_server_adv/thorvg_svg_in_ot.cpp index 2f48f1564c..951ca15503 100644 --- a/modules/text_server_adv/thorvg_svg_in_ot.cpp +++ b/modules/text_server_adv/thorvg_svg_in_ot.cpp @@ -155,21 +155,10 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin } String xml_code_str = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"" + rtos(min_x) + " " + rtos(min_y) + " " + rtos(new_w) + " " + rtos(new_h) + "\">" + xml_body; -#ifndef GDEXTENSION gl_state.xml_code = xml_code_str.utf8(); -#else - CharString xml_code = xml_code_str.utf8(); - gl_state.xml_code_length = xml_code.length(); - gl_state.xml_code = memnew_arr(char, gl_state.xml_code_length); - memcpy(gl_state.xml_code, xml_code.get_data(), gl_state.xml_code_length); -#endif picture = tvg::Picture::gen(); -#ifndef GDEXTENSION result = picture->load(gl_state.xml_code.get_data(), gl_state.xml_code.length(), "svg+xml", false); -#else - result = picture->load(gl_state.xml_code, gl_state.xml_code_length, "svg+xml", false); -#endif if (result != tvg::Result::Success) { ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph metrics)."); } @@ -257,11 +246,7 @@ FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) { ERR_FAIL_COND_V_MSG(!gl_state.ready, FT_Err_Invalid_SVG_Document, "SVG glyph not ready."); std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen(); -#ifndef GDEXTENSION tvg::Result res = picture->load(gl_state.xml_code.get_data(), gl_state.xml_code.length(), "svg+xml", false); -#else - tvg::Result res = picture->load(gl_state.xml_code, gl_state.xml_code_length, "svg+xml", false); -#endif if (res != tvg::Result::Success) { ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph rendering)."); } diff --git a/modules/text_server_adv/thorvg_svg_in_ot.h b/modules/text_server_adv/thorvg_svg_in_ot.h index 5e79c8e444..034fffb5e6 100644 --- a/modules/text_server_adv/thorvg_svg_in_ot.h +++ b/modules/text_server_adv/thorvg_svg_in_ot.h @@ -66,22 +66,8 @@ struct GL_State { float y = 0; float w = 0; float h = 0; -#ifndef GDEXTENSION CharString xml_code; -#else - // @todo After godot-cpp#1142 is fixed, use CharString just like when compiled as a Godot module. - char *xml_code = nullptr; - int xml_code_length = 0; -#endif tvg::Matrix m; - -#ifdef GDEXTENSION - ~GL_State() { - if (xml_code) { - memdelete_arr(xml_code); - } - } -#endif }; struct TVG_State { diff --git a/modules/zip/doc_classes/ZIPReader.xml b/modules/zip/doc_classes/ZIPReader.xml index 0fa2672044..bf596806b2 100644 --- a/modules/zip/doc_classes/ZIPReader.xml +++ b/modules/zip/doc_classes/ZIPReader.xml @@ -25,6 +25,15 @@ Closes the underlying resources used by this instance. </description> </method> + <method name="file_exists"> + <return type="bool" /> + <param index="0" name="path" type="String" /> + <param index="1" name="case_sensitive" type="bool" default="true" /> + <description> + Returns [code]true[/code] if the file exists in the loaded zip archive. + Must be called after [method open]. + </description> + </method> <method name="get_files"> <return type="PackedStringArray" /> <description> diff --git a/modules/zip/zip_reader.cpp b/modules/zip/zip_reader.cpp index 2684875e1c..5752b829ef 100644 --- a/modules/zip/zip_reader.cpp +++ b/modules/zip/zip_reader.cpp @@ -118,6 +118,21 @@ PackedByteArray ZIPReader::read_file(String p_path, bool p_case_sensitive) { return data; } +bool ZIPReader::file_exists(String p_path, bool p_case_sensitive) { + ERR_FAIL_COND_V_MSG(fa.is_null(), false, "ZIPReader must be opened before use."); + + int cs = p_case_sensitive ? 1 : 2; + if (unzLocateFile(uzf, p_path.utf8().get_data(), cs) != UNZ_OK) { + return false; + } + if (unzOpenCurrentFile(uzf) != UNZ_OK) { + return false; + } + + unzCloseCurrentFile(uzf); + return true; +} + ZIPReader::ZIPReader() {} ZIPReader::~ZIPReader() { @@ -131,4 +146,5 @@ void ZIPReader::_bind_methods() { ClassDB::bind_method(D_METHOD("close"), &ZIPReader::close); ClassDB::bind_method(D_METHOD("get_files"), &ZIPReader::get_files); ClassDB::bind_method(D_METHOD("read_file", "path", "case_sensitive"), &ZIPReader::read_file, DEFVAL(Variant(true))); + ClassDB::bind_method(D_METHOD("file_exists", "path", "case_sensitive"), &ZIPReader::file_exists, DEFVAL(Variant(true))); } diff --git a/modules/zip/zip_reader.h b/modules/zip/zip_reader.h index c074197eb4..0f78352e3f 100644 --- a/modules/zip/zip_reader.h +++ b/modules/zip/zip_reader.h @@ -51,6 +51,7 @@ public: PackedStringArray get_files(); PackedByteArray read_file(String p_path, bool p_case_sensitive); + bool file_exists(String p_path, bool p_case_sensitive); ZIPReader(); ~ZIPReader(); diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml index c0c57cafbe..d270980d72 100644 --- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml +++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml @@ -110,6 +110,10 @@ <member name="package/show_in_android_tv" type="bool" setter="" getter=""> If [code]true[/code], this app will show in Android TV launcher UI. </member> + <member name="package/show_in_app_library" type="bool" setter="" getter=""> + If [code]true[/code], this app will show in the device's app library. + [b]Note:[/b] This is [code]true[/code] by default. + </member> <member name="package/signed" type="bool" setter="" getter=""> If [code]true[/code], package signing is enabled. </member> diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 9e46085b2a..20aaed1e3e 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -1838,6 +1838,7 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/retain_data_on_uninstall"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/exclude_from_recents"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/show_in_android_tv"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/show_in_app_library"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "package/show_as_launcher_app"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, launcher_icon_option, PROPERTY_HINT_FILE, "*.png"), "")); @@ -2225,17 +2226,15 @@ String EditorExportPlatformAndroid::get_apksigner_path(int p_target_sdk, bool p_ } bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { -#ifdef MODULE_MONO_ENABLED - // Don't check for additional errors, as this particular error cannot be resolved. - r_error += TTR("Exporting to Android is currently not supported in Godot 4 when using C#/.NET. Use Godot 3 to target Android with C#/Mono instead.") + "\n"; - r_error += TTR("If this project does not use C#, use a non-C# editor build to export the project.") + "\n"; - return false; -#else - String err; bool valid = false; const bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build"); +#ifdef MODULE_MONO_ENABLED + // Android export is still a work in progress, keep a message as a warning. + err += TTR("Exporting to Android when using C#/.NET is experimental.") + "\n"; +#endif + // Look for export templates (first official, and if defined custom templates). if (!gradle_build_enabled) { @@ -2365,7 +2364,6 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito } return valid; -#endif // !MODULE_MONO_ENABLED } bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const { diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index d0d0c34bb4..0915009235 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -269,7 +269,12 @@ String _get_activity_tag(const Ref<EditorExportPlatform> &p_export_platform, con manifest_activity_text += " <intent-filter>\n" " <action android:name=\"android.intent.action.MAIN\" />\n" - " <category android:name=\"android.intent.category.LAUNCHER\" />\n"; + " <category android:name=\"android.intent.category.DEFAULT\" />\n"; + + bool show_in_app_library = p_preset->get("package/show_in_app_library"); + if (show_in_app_library) { + manifest_activity_text += " <category android:name=\"android.intent.category.LAUNCHER\" />\n"; + } bool uses_leanback_category = p_preset->get("package/show_in_android_tv"); if (uses_leanback_category) { @@ -279,7 +284,6 @@ String _get_activity_tag(const Ref<EditorExportPlatform> &p_export_platform, con bool uses_home_category = p_preset->get("package/show_as_launcher_app"); if (uses_home_category) { manifest_activity_text += " <category android:name=\"android.intent.category.HOME\" />\n"; - manifest_activity_text += " <category android:name=\"android.intent.category.DEFAULT\" />\n"; } manifest_activity_text += " </intent-filter>\n"; diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h index 7ff5c4d117..3aa4ca98fc 100644 --- a/platform/android/file_access_android.h +++ b/platform/android/file_access_android.h @@ -76,8 +76,13 @@ public: virtual bool file_exists(const String &p_path) override; // return true if a file exists virtual uint64_t _get_modified_time(const String &p_file) override { return 0; } - virtual uint32_t _get_unix_permissions(const String &p_file) override { return 0; } - virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) override { return FAILED; } + virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; } + virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override { return FAILED; } + + virtual bool _get_hidden_attribute(const String &p_file) override { return false; } + virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override { return ERR_UNAVAILABLE; } + virtual bool _get_read_only_attribute(const String &p_file) override { return false; } + virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override { return ERR_UNAVAILABLE; } virtual void close() override; diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h index 739c8a0925..0c3f8d7259 100644 --- a/platform/android/file_access_filesystem_jandroid.h +++ b/platform/android/file_access_filesystem_jandroid.h @@ -91,8 +91,13 @@ public: static void setup(jobject p_file_access_handler); virtual uint64_t _get_modified_time(const String &p_file) override; - virtual uint32_t _get_unix_permissions(const String &p_file) override { return 0; } - virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) override { return FAILED; } + virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; } + virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override { return FAILED; } + + virtual bool _get_hidden_attribute(const String &p_file) override { return false; } + virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override { return ERR_UNAVAILABLE; } + virtual bool _get_read_only_attribute(const String &p_file) override { return false; } + virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override { return ERR_UNAVAILABLE; } virtual void close() override; diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index 542ab51660..56d403a263 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -45,6 +45,7 @@ <intent-filter> <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml index c7b2c8ad67..cb89d6e1b0 100644 --- a/platform/android/java/editor/src/main/AndroidManifest.xml +++ b/platform/android/java/editor/src/main/AndroidManifest.xml @@ -45,6 +45,7 @@ <intent-filter> <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index e634f28e76..f0864f5134 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -379,7 +379,11 @@ void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { if (show_cursor && !previously_shown) { WindowID window_id = get_window_at_screen_position(mouse_get_position()); - if (window_id != INVALID_WINDOW_ID) { + if (window_id != INVALID_WINDOW_ID && window_mouseover_id != window_id) { + if (window_mouseover_id != INVALID_WINDOW_ID) { + _send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT); + } + window_mouseover_id = window_id; _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER); } } @@ -1449,6 +1453,11 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) { DEBUG_LOG_X11("delete_sub_window: %lu (%u) \n", wd.x11_window, p_id); + if (window_mouseover_id == p_id) { + window_mouseover_id = INVALID_WINDOW_ID; + _send_window_event(windows[p_id], WINDOW_EVENT_MOUSE_EXIT); + } + window_set_rect_changed_callback(Callable(), p_id); window_set_window_event_callback(Callable(), p_id); window_set_input_event_callback(Callable(), p_id); @@ -4292,7 +4301,8 @@ void DisplayServerX11::process_events() { break; } - if (!mouse_mode_grab) { + if (!mouse_mode_grab && window_mouseover_id == window_id) { + window_mouseover_id = INVALID_WINDOW_ID; _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT); } @@ -4304,7 +4314,11 @@ void DisplayServerX11::process_events() { break; } - if (!mouse_mode_grab) { + if (!mouse_mode_grab && window_mouseover_id != window_id) { + if (window_mouseover_id != INVALID_WINDOW_ID) { + _send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT); + } + window_mouseover_id = window_id; _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER); } } break; diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index 74aa0b6e75..cce44a92de 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -225,6 +225,7 @@ class DisplayServerX11 : public DisplayServer { List<WindowID> popup_list; + WindowID window_mouseover_id = INVALID_WINDOW_ID; WindowID last_focused_window = INVALID_WINDOW_ID; WindowID window_id_counter = MAIN_WINDOW_ID; diff --git a/platform/linuxbsd/x11/gl_manager_x11.h b/platform/linuxbsd/x11/gl_manager_x11.h index 59e20fec45..d3a25506a8 100644 --- a/platform/linuxbsd/x11/gl_manager_x11.h +++ b/platform/linuxbsd/x11/gl_manager_x11.h @@ -74,17 +74,17 @@ private: }; struct GLDisplay { - GLDisplay() { context = nullptr; } + GLDisplay() {} ~GLDisplay(); GLManager_X11_Private *context = nullptr; - ::Display *x11_display; - XVisualInfo x_vi; + ::Display *x11_display = nullptr; + XVisualInfo x_vi = {}; }; // just for convenience, window and display struct struct XWinDisp { ::Window x11_window; - ::Display *x11_display; + ::Display *x11_display = nullptr; } _x_windisp; LocalVector<GLWindow> _windows; diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index e5e0e53bfb..69f6008043 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -171,6 +171,7 @@ private: int current_layout = 0; bool keyboard_layout_dirty = true; + WindowID window_mouseover_id = INVALID_WINDOW_ID; WindowID last_focused_window = INVALID_WINDOW_ID; WindowID window_id_counter = MAIN_WINDOW_ID; float display_max_scale = 1.f; @@ -240,6 +241,8 @@ public: bool get_is_resizing() const; void reparent_check(WindowID p_window); WindowID _get_focused_window_or_popup() const; + void mouse_enter_window(WindowID p_window); + void mouse_exit_window(WindowID p_window); void window_update(WindowID p_window); void window_destroy(WindowID p_window); diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index de191827f5..e79d6acc3f 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -366,6 +366,25 @@ DisplayServer::WindowID DisplayServerMacOS::_get_focused_window_or_popup() const return last_focused_window; } +void DisplayServerMacOS::mouse_enter_window(WindowID p_window) { + if (window_mouseover_id != p_window) { + if (window_mouseover_id != INVALID_WINDOW_ID) { + send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT); + } + window_mouseover_id = p_window; + if (p_window != INVALID_WINDOW_ID) { + send_window_event(windows[p_window], WINDOW_EVENT_MOUSE_ENTER); + } + } +} + +void DisplayServerMacOS::mouse_exit_window(WindowID p_window) { + if (window_mouseover_id == p_window && p_window != INVALID_WINDOW_ID) { + send_window_event(windows[p_window], WINDOW_EVENT_MOUSE_EXIT); + } + window_mouseover_id = INVALID_WINDOW_ID; +} + void DisplayServerMacOS::_dispatch_input_events(const Ref<InputEvent> &p_event) { ((DisplayServerMacOS *)(get_singleton()))->_dispatch_input_event(p_event); } @@ -2069,9 +2088,7 @@ void DisplayServerMacOS::mouse_set_mode(MouseMode p_mode) { if (show_cursor && !previously_shown) { window_id = get_window_at_screen_position(mouse_get_position()); - if (window_id != INVALID_WINDOW_ID) { - send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER); - } + mouse_enter_window(window_id); } if (p_mode == MOUSE_MODE_CAPTURED) { diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm index 3467bf90a1..231be83a03 100644 --- a/platform/macos/godot_content_view.mm +++ b/platform/macos/godot_content_view.mm @@ -505,9 +505,8 @@ return; } - DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_CAPTURED) { - ds->send_window_event(wd, DisplayServerMacOS::WINDOW_EVENT_MOUSE_EXIT); + ds->mouse_exit_window(window_id); } } @@ -517,9 +516,8 @@ return; } - DisplayServerMacOS::WindowData &wd = ds->get_window(window_id); if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_CAPTURED) { - ds->send_window_event(wd, DisplayServerMacOS::WINDOW_EVENT_MOUSE_ENTER); + ds->mouse_enter_window(window_id); } ds->cursor_update_shape(); diff --git a/platform/macos/godot_window_delegate.mm b/platform/macos/godot_window_delegate.mm index 1c6dbb1981..46355b4ae8 100644 --- a/platform/macos/godot_window_delegate.mm +++ b/platform/macos/godot_window_delegate.mm @@ -67,6 +67,7 @@ ds->window_set_transient(window_id, DisplayServerMacOS::INVALID_WINDOW_ID); } + ds->mouse_exit_window(window_id); ds->window_destroy(window_id); } diff --git a/platform/windows/detect.py b/platform/windows/detect.py index bec1fd2cb6..9548939695 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -355,6 +355,9 @@ def configure_msvc(env, vcvars_msvc_config): else: env.AppendUnique(CCFLAGS=["/MD"]) + # MSVC incremental linking is broken and _increases_ link time (GH-77968). + env.Append(LINKFLAGS=["/INCREMENTAL:NO"]) + if env["arch"] == "x86_32": env["x86_libtheora_opt_vc"] = True diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp index 07c6a8d6e4..c4be1821bd 100644 --- a/platform/windows/export/export_plugin.cpp +++ b/platform/windows/export/export_plugin.cpp @@ -168,10 +168,10 @@ Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPres Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { if (p_preset->get("application/modify_resources")) { - _rcedit_add_data(p_preset, p_path, true); + _rcedit_add_data(p_preset, p_path, false); String wrapper_path = p_path.get_basename() + ".console.exe"; if (FileAccess::exists(wrapper_path)) { - _rcedit_add_data(p_preset, wrapper_path, false); + _rcedit_add_data(p_preset, wrapper_path, true); } } return OK; diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index e7003ab98b..78987738a5 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -33,20 +33,21 @@ #include "core/config/project_settings.h" #include "scene/main/window.h" +bool Camera2D::_is_editing_in_editor() const { +#ifdef TOOLS_ENABLED + return is_part_of_edited_scene(); +#else + return false; +#endif // TOOLS_ENABLED +} + void Camera2D::_update_scroll() { - if (!is_inside_tree()) { + if (!is_inside_tree() || !viewport) { return; } - if (Engine::get_singleton()->is_editor_hint()) { + if (_is_editing_in_editor()) { queue_redraw(); - // Only set viewport transform when not bound to the main viewport. - if (get_tree()->get_edited_scene_root() && get_viewport() == get_tree()->get_edited_scene_root()->get_viewport()) { - return; - } - } - - if (!viewport) { return; } @@ -65,7 +66,7 @@ void Camera2D::_update_scroll() { } void Camera2D::_update_process_callback() { - if (Engine::get_singleton()->is_editor_hint()) { + if (_is_editing_in_editor()) { set_process_internal(false); set_physics_process_internal(false); } else if (process_callback == CAMERA2D_PROCESS_IDLE) { @@ -106,7 +107,7 @@ Transform2D Camera2D::get_camera_transform() { if (!first) { if (anchor_mode == ANCHOR_MODE_DRAG_CENTER) { - if (drag_horizontal_enabled && !Engine::get_singleton()->is_editor_hint() && !drag_horizontal_offset_changed) { + if (drag_horizontal_enabled && !_is_editing_in_editor() && !drag_horizontal_offset_changed) { camera_pos.x = MIN(camera_pos.x, (new_camera_pos.x + screen_size.x * 0.5 * zoom_scale.x * drag_margin[SIDE_LEFT])); camera_pos.x = MAX(camera_pos.x, (new_camera_pos.x - screen_size.x * 0.5 * zoom_scale.x * drag_margin[SIDE_RIGHT])); } else { @@ -119,7 +120,7 @@ Transform2D Camera2D::get_camera_transform() { drag_horizontal_offset_changed = false; } - if (drag_vertical_enabled && !Engine::get_singleton()->is_editor_hint() && !drag_vertical_offset_changed) { + if (drag_vertical_enabled && !_is_editing_in_editor() && !drag_vertical_offset_changed) { camera_pos.y = MIN(camera_pos.y, (new_camera_pos.y + screen_size.y * 0.5 * zoom_scale.y * drag_margin[SIDE_TOP])); camera_pos.y = MAX(camera_pos.y, (new_camera_pos.y - screen_size.y * 0.5 * zoom_scale.y * drag_margin[SIDE_BOTTOM])); @@ -158,7 +159,7 @@ Transform2D Camera2D::get_camera_transform() { } } - if (position_smoothing_enabled && !Engine::get_singleton()->is_editor_hint()) { + if (position_smoothing_enabled && !_is_editing_in_editor()) { real_t c = position_smoothing_speed * (process_callback == CAMERA2D_PROCESS_PHYSICS ? get_physics_process_delta_time() : get_process_delta_time()); smoothed_camera_pos = ((camera_pos - smoothed_camera_pos) * c) + smoothed_camera_pos; ret_camera_pos = smoothed_camera_pos; @@ -175,7 +176,7 @@ Transform2D Camera2D::get_camera_transform() { Point2 screen_offset = (anchor_mode == ANCHOR_MODE_DRAG_CENTER ? (screen_size * 0.5 * zoom_scale) : Point2()); if (!ignore_rotation) { - if (rotation_smoothing_enabled && !Engine::get_singleton()->is_editor_hint()) { + if (rotation_smoothing_enabled && !_is_editing_in_editor()) { real_t step = rotation_smoothing_speed * (process_callback == CAMERA2D_PROCESS_PHYSICS ? get_physics_process_delta_time() : get_process_delta_time()); camera_angle = Math::lerp_angle(camera_angle, get_global_rotation(), step); } else { @@ -250,7 +251,7 @@ void Camera2D::_notification(int p_what) { add_to_group(group_name); add_to_group(canvas_group_name); - if (!Engine::get_singleton()->is_editor_hint() && enabled && !viewport->get_camera_2d()) { + if (!_is_editing_in_editor() && enabled && !viewport->get_camera_2d()) { make_current(); } @@ -272,7 +273,7 @@ void Camera2D::_notification(int p_what) { #ifdef TOOLS_ENABLED case NOTIFICATION_DRAW: { - if (!is_inside_tree() || !Engine::get_singleton()->is_editor_hint()) { + if (!is_inside_tree() || !_is_editing_in_editor()) { break; } @@ -398,7 +399,11 @@ void Camera2D::set_process_callback(Camera2DProcessCallback p_mode) { void Camera2D::set_enabled(bool p_enabled) { enabled = p_enabled; - if (enabled && is_inside_tree() && !viewport->get_camera_2d()) { + if (!is_inside_tree()) { + return; + } + + if (enabled && !viewport->get_camera_2d()) { make_current(); } else if (!enabled && is_current()) { clear_current(); @@ -414,27 +419,27 @@ Camera2D::Camera2DProcessCallback Camera2D::get_process_callback() const { } void Camera2D::_make_current(Object *p_which) { - if (!viewport || (custom_viewport && !ObjectDB::get_instance(custom_viewport_id))) { + if (!is_inside_tree() || !viewport) { return; } + if (custom_viewport && !ObjectDB::get_instance(custom_viewport_id)) { + return; + } + + queue_redraw(); + if (p_which == this) { - if (is_inside_tree()) { - viewport->_camera_2d_set(this); - queue_redraw(); - } + viewport->_camera_2d_set(this); } else { - if (is_inside_tree()) { - if (viewport->get_camera_2d() == this) { - viewport->_camera_2d_set(nullptr); - } - queue_redraw(); + if (viewport->get_camera_2d() == this) { + viewport->_camera_2d_set(nullptr); } } } void Camera2D::_update_process_internal_for_smoothing() { - bool is_not_in_scene_or_editor = !(is_inside_tree() && Engine::get_singleton()->is_editor_hint()); + bool is_not_in_scene_or_editor = !(is_inside_tree() && _is_editing_in_editor()); bool is_any_smoothing_valid = position_smoothing_speed > 0 || rotation_smoothing_speed > 0; bool enable = is_any_smoothing_valid && is_not_in_scene_or_editor; @@ -453,13 +458,22 @@ void Camera2D::make_current() { void Camera2D::clear_current() { ERR_FAIL_COND(!is_current()); - if (viewport && !(custom_viewport && !ObjectDB::get_instance(custom_viewport_id)) && viewport->is_inside_tree()) { + + if (!viewport || !viewport->is_inside_tree()) { + return; + } + + if (!custom_viewport || ObjectDB::get_instance(custom_viewport_id)) { viewport->assign_next_enabled_camera_2d(group_name); } } bool Camera2D::is_current() const { - if (viewport && !(custom_viewport && !ObjectDB::get_instance(custom_viewport_id))) { + if (!viewport) { + return false; + } + + if (!custom_viewport || ObjectDB::get_instance(custom_viewport_id)) { return viewport->get_camera_2d() == this; } return false; @@ -567,8 +581,7 @@ Point2 Camera2D::get_camera_screen_center() const { } Size2 Camera2D::_get_camera_screen_size() const { - // special case if the camera2D is in the root viewport - if (Engine::get_singleton()->is_editor_hint() && get_viewport()->get_parent_viewport() == get_tree()->get_root()) { + if (_is_editing_in_editor()) { return Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); } return get_viewport_rect().size; diff --git a/scene/2d/camera_2d.h b/scene/2d/camera_2d.h index 808529b0fb..5693d05ee5 100644 --- a/scene/2d/camera_2d.h +++ b/scene/2d/camera_2d.h @@ -85,6 +85,7 @@ protected: bool drag_vertical_offset_changed = false; Point2 camera_screen_center; + bool _is_editing_in_editor() const; void _update_process_callback(); void _update_scroll(); diff --git a/scene/2d/canvas_modulate.cpp b/scene/2d/canvas_modulate.cpp index 7dd2e75f09..2c5c6a1a16 100644 --- a/scene/2d/canvas_modulate.cpp +++ b/scene/2d/canvas_modulate.cpp @@ -30,32 +30,67 @@ #include "canvas_modulate.h" +void CanvasModulate::_on_in_canvas_visibility_changed(bool p_new_visibility) { + RID canvas = get_canvas(); + StringName group_name = "_canvas_modulate_" + itos(canvas.get_id()); + + ERR_FAIL_COND_MSG(p_new_visibility == is_in_group(group_name), vformat("CanvasModulate becoming %s in the canvas already %s in the modulate group. Buggy logic, please report.", p_new_visibility ? "visible" : "invisible", p_new_visibility ? "was" : "was not")); + + if (p_new_visibility) { + bool has_active_canvas_modulate = get_tree()->has_group(group_name); // Group would be removed if empty; otherwise one CanvasModulate within must be active. + add_to_group(group_name); + if (!has_active_canvas_modulate) { + is_active = true; + RS::get_singleton()->canvas_set_modulate(canvas, color); + } + } else { + remove_from_group(group_name); + if (is_active) { + is_active = false; + CanvasModulate *new_active = Object::cast_to<CanvasModulate>(get_tree()->get_first_node_in_group(group_name)); + if (new_active) { + new_active->is_active = true; + RS::get_singleton()->canvas_set_modulate(canvas, new_active->color); + } else { + RS::get_singleton()->canvas_set_modulate(canvas, Color(1, 1, 1, 1)); + } + } + } + + update_configuration_warnings(); +} + void CanvasModulate::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_CANVAS: { - if (is_visible_in_tree()) { - RS::get_singleton()->canvas_set_modulate(get_canvas(), color); - add_to_group("_canvas_modulate_" + itos(get_canvas().get_id())); + is_in_canvas = true; + bool visible_in_tree = is_visible_in_tree(); + if (visible_in_tree) { + _on_in_canvas_visibility_changed(true); } + was_visible_in_tree = visible_in_tree; } break; case NOTIFICATION_EXIT_CANVAS: { - if (is_visible_in_tree()) { - RS::get_singleton()->canvas_set_modulate(get_canvas(), Color(1, 1, 1, 1)); - remove_from_group("_canvas_modulate_" + itos(get_canvas().get_id())); + is_in_canvas = false; + if (was_visible_in_tree) { + _on_in_canvas_visibility_changed(false); } } break; case NOTIFICATION_VISIBILITY_CHANGED: { - if (is_visible_in_tree()) { - RS::get_singleton()->canvas_set_modulate(get_canvas(), color); - add_to_group("_canvas_modulate_" + itos(get_canvas().get_id())); - } else { - RS::get_singleton()->canvas_set_modulate(get_canvas(), Color(1, 1, 1, 1)); - remove_from_group("_canvas_modulate_" + itos(get_canvas().get_id())); + if (!is_in_canvas) { + return; } - update_configuration_warnings(); + bool visible_in_tree = is_visible_in_tree(); + if (visible_in_tree == was_visible_in_tree) { + return; + } + + _on_in_canvas_visibility_changed(visible_in_tree); + + was_visible_in_tree = visible_in_tree; } break; } } @@ -69,7 +104,7 @@ void CanvasModulate::_bind_methods() { void CanvasModulate::set_color(const Color &p_color) { color = p_color; - if (is_visible_in_tree()) { + if (is_active) { RS::get_singleton()->canvas_set_modulate(get_canvas(), color); } } @@ -81,12 +116,12 @@ Color CanvasModulate::get_color() const { PackedStringArray CanvasModulate::get_configuration_warnings() const { PackedStringArray warnings = Node::get_configuration_warnings(); - if (is_visible_in_tree() && is_inside_tree()) { + if (is_in_canvas && is_visible_in_tree()) { List<Node *> nodes; get_tree()->get_nodes_in_group("_canvas_modulate_" + itos(get_canvas().get_id()), &nodes); if (nodes.size() > 1) { - warnings.push_back(RTR("Only one visible CanvasModulate is allowed per scene (or set of instantiated scenes). The first created one will work, while the rest will be ignored.")); + warnings.push_back(RTR("Only one visible CanvasModulate is allowed per canvas.\nWhen there are more than one, only one of them will be active. Which one is undefined.")); } } diff --git a/scene/2d/canvas_modulate.h b/scene/2d/canvas_modulate.h index 3b11cf71f1..08ded52e23 100644 --- a/scene/2d/canvas_modulate.h +++ b/scene/2d/canvas_modulate.h @@ -38,6 +38,14 @@ class CanvasModulate : public Node2D { Color color = Color(1, 1, 1, 1); + // CanvasModulate is in canvas-specific modulate group when both in canvas and visible in tree. + // Exactly one CanvasModulate in each such non-empty group is active. + bool is_in_canvas = false; + bool was_visible_in_tree = false; // Relevant only when in canvas. + bool is_active = false; + + void _on_in_canvas_visibility_changed(bool p_new_visibility); + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 1836cc20b6..09effe6596 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -1839,6 +1839,17 @@ void TileMapLayer::set_cell(const Vector2i &p_coords, int p_source_id, const Vec ERR_FAIL_COND(!Q); TileMapQuadrant &q = Q->value; + // Find node in scenes and remove it. + HashMap<Vector2i, String>::Iterator entry = q.scenes.find(pk); + if (entry != q.scenes.end()) { + String scene_name = entry->value; + Node *scene = tile_map_node->get_node_or_null(scene_name); + if (scene) { + scene->queue_free(); + instantiated_scenes.erase(Vector2i(pk.x, pk.y)); + } + } + q.cells.erase(pk); // Remove or make the quadrant dirty. diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 68241337c9..443a639ff2 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -3128,6 +3128,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { } String target_lower = option.display.to_lower(); + int long_option = target_lower.size() > 50; const char32_t *string_to_complete_char_lower = &string_to_complete_lower[0]; const char32_t *target_char_lower = &target_lower[0]; @@ -3142,27 +3143,34 @@ void CodeEdit::_filter_code_completion_candidates_impl() { for (int i = 1; *string_to_complete_char_lower && (all_possible_subsequence_matches.size() > 0); i++, string_to_complete_char_lower++) { // find all occurrences of ssq_lower to avoid looking everywhere each time Vector<int> all_ocurence; - for (int j = i; j < target_lower.length(); j++) { - if (target_lower[j] == *string_to_complete_char_lower) { - all_ocurence.push_back(j); + if (long_option) { + all_ocurence.push_back(target_lower.find_char(*string_to_complete_char_lower)); + } else { + for (int j = i; j < target_lower.length(); j++) { + if (target_lower[j] == *string_to_complete_char_lower) { + all_ocurence.push_back(j); + } } } Vector<Vector<Pair<int, int>>> next_subsequence_matches; - for (Vector<Pair<int, int>> &subsequence_matches : all_possible_subsequence_matches) { - Pair<int, int> match_last_segment = subsequence_matches[subsequence_matches.size() - 1]; + for (Vector<Pair<int, int>> &subsequence_match : all_possible_subsequence_matches) { + Pair<int, int> match_last_segment = subsequence_match[subsequence_match.size() - 1]; int next_index = match_last_segment.first + match_last_segment.second; // get the last index from current sequence // and look for next char starting from that index if (target_lower[next_index] == *string_to_complete_char_lower) { - Vector<Pair<int, int>> new_matches = subsequence_matches; - new_matches.write[new_matches.size() - 1].second++; - next_subsequence_matches.push_back(new_matches); + Vector<Pair<int, int>> new_match = subsequence_match; + new_match.write[new_match.size() - 1].second++; + next_subsequence_matches.push_back(new_match); + if (long_option) { + continue; + } } for (int index : all_ocurence) { if (index > next_index) { - Vector<Pair<int, int>> new_matches = subsequence_matches; - new_matches.push_back({ index, 1 }); - next_subsequence_matches.push_back(new_matches); + Vector<Pair<int, int>> new_match = subsequence_match; + new_match.push_back({ index, 1 }); + next_subsequence_matches.push_back(new_match); } } } diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index 4f94f0d9f2..6e75be268d 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -38,8 +38,7 @@ // AcceptDialog void AcceptDialog::_input_from_window(const Ref<InputEvent> &p_event) { - Ref<InputEventKey> key = p_event; - if (close_on_escape && key.is_valid() && key->is_action_pressed(SNAME("ui_cancel"), false, true)) { + if (close_on_escape && p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) { _cancel_pressed(); } } diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 614e9b9695..d86cc4a5f9 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -227,7 +227,6 @@ private: void _zoom_plus(); void _update_zoom_label(); - PackedVector2Array get_connection_line(const Vector2 &p_from, const Vector2 &p_to); void _draw_connection_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_zoom); void _graph_node_selected(Node *p_gn); @@ -243,8 +242,6 @@ private: virtual void gui_input(const Ref<InputEvent> &p_ev) override; void _top_layer_input(const Ref<InputEvent> &p_ev); - bool is_in_input_hotzone(GraphNode *p_graph_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size); - bool is_in_output_hotzone(GraphNode *p_graph_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size); bool is_in_port_hotzone(const Vector2 &p_pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left); void _top_layer_draw(); @@ -272,6 +269,9 @@ protected: void _notification(int p_what); + virtual bool is_in_input_hotzone(GraphNode *p_graph_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size); + virtual bool is_in_output_hotzone(GraphNode *p_graph_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size); + GDVIRTUAL2RC(Vector<Vector2>, _get_connection_line, Vector2, Vector2) GDVIRTUAL3R(bool, _is_in_input_hotzone, Object *, int, Vector2) GDVIRTUAL3R(bool, _is_in_output_hotzone, Object *, int, Vector2) @@ -287,6 +287,8 @@ public: void disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); void clear_connections(); void force_connection_drag_end(); + + virtual PackedVector2Array get_connection_line(const Vector2 &p_from, const Vector2 &p_to); virtual bool is_node_hover_valid(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); void set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity); diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index c0a2dc81d0..6915f3d242 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -35,8 +35,7 @@ #include "scene/gui/panel.h" void Popup::_input_from_window(const Ref<InputEvent> &p_event) { - Ref<InputEventKey> key = p_event; - if (get_flag(FLAG_POPUP) && key.is_valid() && key->is_action_pressed(SNAME("ui_cancel"), false, true)) { + if (get_flag(FLAG_POPUP) && p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) { _close_pressed(); } } diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 3a0fb6d89c..1e7e376fc8 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -2745,6 +2745,9 @@ void RichTextLabel::_thread_function(void *p_userdata) { void RichTextLabel::_thread_end() { set_physics_process_internal(false); + if (!scroll_visible) { + vscroll->hide(); + } if (is_visible_in_tree()) { queue_redraw(); } @@ -2814,7 +2817,6 @@ _FORCE_INLINE_ float RichTextLabel::_update_scroll_exceeds(float p_total_height, } else { scroll_visible = false; scroll_w = 0; - vscroll->hide(); } main->first_resized_line.store(0); @@ -2862,6 +2864,9 @@ bool RichTextLabel::_validate_line_caches() { if (main->first_resized_line.load() == (int)main->lines.size()) { vscroll->set_value(old_scroll); validating.store(false); + if (!scroll_visible) { + vscroll->hide(); + } return true; } @@ -2881,6 +2886,9 @@ bool RichTextLabel::_validate_line_caches() { update_minimum_size(); } validating.store(false); + if (!scroll_visible) { + vscroll->hide(); + } return true; } validating.store(false); @@ -2896,6 +2904,9 @@ bool RichTextLabel::_validate_line_caches() { updating.store(true); _process_line_caches(); updating.store(false); + if (!scroll_visible) { + vscroll->hide(); + } queue_redraw(); return true; } diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index fcf9302953..06b3882d25 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -428,6 +428,14 @@ void ScrollBar::_notification(int p_what) { } } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible()) { + incr_active = false; + decr_active = false; + drag.active = false; + } + } break; + case NOTIFICATION_MOUSE_EXIT: { highlight = HIGHLIGHT_NONE; queue_redraw(); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 433ae656ba..3c1be2d5fe 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -1922,7 +1922,7 @@ void Tree::update_column(int p_col) { columns.write[p_col].text_buf->set_direction((TextServer::Direction)columns[p_col].text_direction); } - columns.write[p_col].text_buf->add_string(columns[p_col].title, theme_cache.font, theme_cache.font_size, columns[p_col].language); + columns.write[p_col].text_buf->add_string(columns[p_col].title, theme_cache.tb_font, theme_cache.tb_font_size, columns[p_col].language); columns.write[p_col].cached_minimum_width_dirty = true; } @@ -4108,7 +4108,7 @@ void Tree::update_scrollbars() { } int Tree::_get_title_button_height() const { - ERR_FAIL_COND_V(theme_cache.font.is_null() || theme_cache.title_button.is_null(), 0); + ERR_FAIL_COND_V(theme_cache.tb_font.is_null() || theme_cache.title_button.is_null(), 0); int h = 0; if (show_column_titles) { for (int i = 0; i < columns.size(); i++) { @@ -4243,7 +4243,6 @@ void Tree::_notification(int p_what) { int ofs2 = theme_cache.panel_style->get_margin(SIDE_LEFT); for (int i = 0; i < columns.size(); i++) { Ref<StyleBox> sb = (cache.click_type == Cache::CLICK_TITLE && cache.click_index == i) ? theme_cache.title_button_pressed : ((cache.hover_type == Cache::CLICK_TITLE && cache.hover_index == i) ? theme_cache.title_button_hover : theme_cache.title_button); - Ref<Font> f = theme_cache.tb_font; Rect2 tbrect = Rect2(ofs2 - theme_cache.offset.x, bg->get_margin(SIDE_TOP), get_column_width(i), tbh); if (cache.rtl) { tbrect.position.x = get_size().width - tbrect.size.x - tbrect.position.x; diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 8d5133311a..3500b2201a 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -1347,6 +1347,10 @@ void Node::_generate_serial_child_name(const Node *p_child, StringName &name) co } } +Node::InternalMode Node::get_internal_mode() const { + return data.internal_mode; +} + void Node::_add_child_nocheck(Node *p_child, const StringName &p_name, InternalMode p_internal_mode) { //add a child node quickly, without name validation diff --git a/scene/main/node.h b/scene/main/node.h index ed8f699d7b..feda200b24 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -386,6 +386,8 @@ public: String get_description() const; void set_name(const String &p_name); + InternalMode get_internal_mode() const; + void add_child(Node *p_child, bool p_force_readable_name = false, InternalMode p_internal = INTERNAL_MODE_DISABLED); void add_sibling(Node *p_sibling, bool p_force_readable_name = false); void remove_child(Node *p_child); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 2d3aa66f2c..180efaaa60 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -2955,7 +2955,7 @@ bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) { void Viewport::_update_mouse_over() { // Update gui.mouse_over and gui.subwindow_over in all Viewports. - // Send necessary mouse_enter/mouse_exit signals and the NOTIFICATION_VP_MOUSE_ENTER/NOTIFICATION_VP_MOUSE_EXIT notifications for every Viewport in the SceneTree. + // Send necessary mouse_enter/mouse_exit signals and the MOUSE_ENTER/MOUSE_EXIT notifications for every Viewport in the SceneTree. if (is_attached_in_viewport()) { // Execute this function only, when it is processed by a native Window or a SubViewport, that has no SubViewportContainer as parent. @@ -3009,7 +3009,7 @@ void Viewport::_update_mouse_over(Vector2 p_pos) { } gui.subwindow_over = sw; if (!sw->is_input_disabled()) { - sw->notification(NOTIFICATION_VP_MOUSE_ENTER); + sw->_propagate_window_notification(sw, NOTIFICATION_WM_MOUSE_ENTER); } } if (!sw->is_input_disabled()) { diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 331ce98cdd..1e107ea99c 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -468,7 +468,8 @@ private: SubWindowResize _sub_window_get_resize_margin(Window *p_subwindow, const Point2 &p_point); void _update_mouse_over(); - void _update_mouse_over(Vector2 p_pos); + virtual void _update_mouse_over(Vector2 p_pos); + virtual void _mouse_leave_viewport(); virtual bool _can_consume_input_events() const { return true; } uint64_t event_count = 0; @@ -482,8 +483,6 @@ protected: Size2i _get_size_2d_override() const; bool _is_size_allocated() const; - void _mouse_leave_viewport(); - void _notification(int p_what); void _process_picking(); static void _bind_methods(); diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 875b53203a..1af279d94c 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -679,7 +679,7 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) { } _propagate_window_notification(this, NOTIFICATION_WM_MOUSE_ENTER); root->gui.windowmanager_window_over = this; - notification(NOTIFICATION_VP_MOUSE_ENTER); + mouse_in_window = true; if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CURSOR_SHAPE)) { DisplayServer::get_singleton()->cursor_set_shape(DisplayServer::CURSOR_ARROW); //restore cursor shape } @@ -692,6 +692,7 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) { #endif // DEV_ENABLED return; } + mouse_in_window = false; root->gui.windowmanager_window_over->_mouse_leave_viewport(); root->gui.windowmanager_window_over = nullptr; _propagate_window_notification(this, NOTIFICATION_WM_MOUSE_EXIT); @@ -1001,6 +1002,17 @@ void Window::_update_viewport_size() { float font_oversampling = 1.0; window_transform = Transform2D(); + if (content_scale_stretch == Window::CONTENT_SCALE_STRETCH_INTEGER) { + // We always want to make sure that the content scale factor is a whole + // number, else there will be pixel wobble no matter what. + content_scale_factor = Math::floor(content_scale_factor); + + // A content scale factor of zero is pretty useless. + if (content_scale_factor < 1) { + content_scale_factor = 1; + } + } + if (content_scale_mode == CONTENT_SCALE_MODE_DISABLED || content_scale_size.x == 0 || content_scale_size.y == 0) { font_oversampling = content_scale_factor; final_size = size; @@ -1054,13 +1066,26 @@ void Window::_update_viewport_size() { screen_size = screen_size.floor(); viewport_size = viewport_size.floor(); + if (content_scale_stretch == Window::CONTENT_SCALE_STRETCH_INTEGER) { + Size2i screen_scale = (screen_size / viewport_size).floor(); + int scale_factor = MIN(screen_scale.x, screen_scale.y); + + if (scale_factor < 1) { + scale_factor = 1; + } + + screen_size = viewport_size * scale_factor; + } + Size2 margin; Size2 offset; - if (content_scale_aspect != CONTENT_SCALE_ASPECT_EXPAND && screen_size.x < video_mode.x) { + if (screen_size.x < video_mode.x) { margin.x = Math::round((video_mode.x - screen_size.x) / 2.0); offset.x = Math::round(margin.x * viewport_size.y / screen_size.y); - } else if (content_scale_aspect != CONTENT_SCALE_ASPECT_EXPAND && screen_size.y < video_mode.y) { + } + + if (screen_size.y < video_mode.y) { margin.y = Math::round((video_mode.y - screen_size.y) / 2.0); offset.y = Math::round(margin.y * viewport_size.x / screen_size.x); } @@ -1337,6 +1362,15 @@ Window::ContentScaleAspect Window::get_content_scale_aspect() const { return content_scale_aspect; } +void Window::set_content_scale_stretch(ContentScaleStretch p_stretch) { + content_scale_stretch = p_stretch; + _update_viewport_size(); +} + +Window::ContentScaleStretch Window::get_content_scale_stretch() const { + return content_scale_stretch; +} + void Window::set_content_scale_factor(real_t p_factor) { ERR_MAIN_THREAD_GUARD; ERR_FAIL_COND(p_factor <= 0); @@ -2519,6 +2553,41 @@ bool Window::is_attached_in_viewport() const { return get_embedder(); } +void Window::_update_mouse_over(Vector2 p_pos) { + if (!mouse_in_window) { + if (is_embedded()) { + mouse_in_window = true; + _propagate_window_notification(this, NOTIFICATION_WM_MOUSE_ENTER); + } else { + // Prevent update based on delayed InputEvents from DisplayServer. + return; + } + } + + bool new_in = get_visible_rect().has_point(p_pos); + if (new_in == gui.mouse_in_viewport) { + if (new_in) { + Viewport::_update_mouse_over(p_pos); + } + return; + } + + if (new_in) { + notification(NOTIFICATION_VP_MOUSE_ENTER); + Viewport::_update_mouse_over(p_pos); + } else { + Viewport::_mouse_leave_viewport(); + } +} + +void Window::_mouse_leave_viewport() { + Viewport::_mouse_leave_viewport(); + if (is_embedded()) { + mouse_in_window = false; + _propagate_window_notification(this, NOTIFICATION_WM_MOUSE_EXIT); + } +} + void Window::_bind_methods() { ClassDB::bind_method(D_METHOD("set_title", "title"), &Window::set_title); ClassDB::bind_method(D_METHOD("get_title"), &Window::get_title); @@ -2593,6 +2662,9 @@ void Window::_bind_methods() { ClassDB::bind_method(D_METHOD("set_content_scale_aspect", "aspect"), &Window::set_content_scale_aspect); ClassDB::bind_method(D_METHOD("get_content_scale_aspect"), &Window::get_content_scale_aspect); + ClassDB::bind_method(D_METHOD("set_content_scale_stretch", "stretch"), &Window::set_content_scale_stretch); + ClassDB::bind_method(D_METHOD("get_content_scale_stretch"), &Window::get_content_scale_stretch); + ClassDB::bind_method(D_METHOD("set_content_scale_factor", "factor"), &Window::set_content_scale_factor); ClassDB::bind_method(D_METHOD("get_content_scale_factor"), &Window::get_content_scale_factor); @@ -2711,7 +2783,8 @@ void Window::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "content_scale_size"), "set_content_scale_size", "get_content_scale_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "content_scale_mode", PROPERTY_HINT_ENUM, "Disabled,Canvas Items,Viewport"), "set_content_scale_mode", "get_content_scale_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "content_scale_aspect", PROPERTY_HINT_ENUM, "Ignore,Keep,Keep Width,Keep Height,Expand"), "set_content_scale_aspect", "get_content_scale_aspect"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "content_scale_factor"), "set_content_scale_factor", "get_content_scale_factor"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "content_scale_stretch", PROPERTY_HINT_ENUM, "Fractional,Integer"), "set_content_scale_stretch", "get_content_scale_stretch"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "content_scale_factor", PROPERTY_HINT_RANGE, "0.5,8.0,0.01"), "set_content_scale_factor", "get_content_scale_factor"); ADD_GROUP("Localization", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate"), "set_auto_translate", "is_auto_translating"); @@ -2763,6 +2836,9 @@ void Window::_bind_methods() { BIND_ENUM_CONSTANT(CONTENT_SCALE_ASPECT_KEEP_HEIGHT); BIND_ENUM_CONSTANT(CONTENT_SCALE_ASPECT_EXPAND); + BIND_ENUM_CONSTANT(CONTENT_SCALE_STRETCH_FRACTIONAL); + BIND_ENUM_CONSTANT(CONTENT_SCALE_STRETCH_INTEGER); + BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_INHERITED); BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LOCALE); BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LTR); diff --git a/scene/main/window.h b/scene/main/window.h index 18ddd89662..c387ffa92a 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -78,6 +78,11 @@ public: CONTENT_SCALE_ASPECT_EXPAND, }; + enum ContentScaleStretch { + CONTENT_SCALE_STRETCH_FRACTIONAL, + CONTENT_SCALE_STRETCH_INTEGER, + }; + enum LayoutDirection { LAYOUT_DIRECTION_INHERITED, LAYOUT_DIRECTION_LOCALE, @@ -135,6 +140,7 @@ private: Size2i content_scale_size; ContentScaleMode content_scale_mode = CONTENT_SCALE_MODE_DISABLED; ContentScaleAspect content_scale_aspect = CONTENT_SCALE_ASPECT_IGNORE; + ContentScaleStretch content_scale_stretch = CONTENT_SCALE_STRETCH_FRACTIONAL; real_t content_scale_factor = 1.0; void _make_window(); @@ -197,6 +203,10 @@ private: void _event_callback(DisplayServer::WindowEvent p_event); virtual bool _can_consume_input_events() const override; + bool mouse_in_window = false; + void _update_mouse_over(Vector2 p_pos) override; + void _mouse_leave_viewport() override; + Ref<Shortcut> debugger_stop_shortcut; protected: @@ -299,6 +309,9 @@ public: void set_content_scale_aspect(ContentScaleAspect p_aspect); ContentScaleAspect get_content_scale_aspect() const; + void set_content_scale_stretch(ContentScaleStretch p_stretch); + ContentScaleStretch get_content_scale_stretch() const; + void set_content_scale_factor(real_t p_factor); real_t get_content_scale_factor() const; @@ -420,6 +433,7 @@ VARIANT_ENUM_CAST(Window::Mode); VARIANT_ENUM_CAST(Window::Flags); VARIANT_ENUM_CAST(Window::ContentScaleMode); VARIANT_ENUM_CAST(Window::ContentScaleAspect); +VARIANT_ENUM_CAST(Window::ContentScaleStretch); VARIANT_ENUM_CAST(Window::LayoutDirection); VARIANT_ENUM_CAST(Window::WindowInitialPosition); diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index b6a1737acb..eef46a6798 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -183,7 +183,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("icon_focus_color", "Button", Color(1, 1, 1, 1)); theme->set_color("icon_disabled_color", "Button", Color(1, 1, 1, 0.4)); - theme->set_constant("h_separation", "Button", Math::round(2 * scale)); + theme->set_constant("h_separation", "Button", Math::round(4 * scale)); theme->set_constant("icon_max_width", "Button", 0); // MenuBar @@ -259,7 +259,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_disabled_color", "OptionButton", control_font_disabled_color); theme->set_color("font_outline_color", "OptionButton", Color(1, 1, 1)); - theme->set_constant("h_separation", "OptionButton", Math::round(2 * scale)); + theme->set_constant("h_separation", "OptionButton", Math::round(4 * scale)); theme->set_constant("arrow_margin", "OptionButton", Math::round(4 * scale)); theme->set_constant("outline_size", "OptionButton", 0); theme->set_constant("modulate_arrow", "OptionButton", false); @@ -282,7 +282,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_disabled_color", "MenuButton", Color(1, 1, 1, 0.3)); theme->set_color("font_outline_color", "MenuButton", Color(1, 1, 1)); - theme->set_constant("h_separation", "MenuButton", Math::round(3 * scale)); + theme->set_constant("h_separation", "MenuButton", Math::round(4 * scale)); theme->set_constant("outline_size", "MenuButton", 0); // CheckBox @@ -762,6 +762,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_font("title_button_font", "Tree", Ref<Font>()); theme->set_font("font", "Tree", Ref<Font>()); theme->set_font_size("font_size", "Tree", -1); + theme->set_font_size("title_button_font_size", "Tree", -1); theme->set_color("title_button_color", "Tree", control_font_color); theme->set_color("font_color", "Tree", control_font_low_color); @@ -788,7 +789,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("children_hl_line_width", "Tree", 1); theme->set_constant("parent_hl_line_margin", "Tree", 0); theme->set_constant("draw_guides", "Tree", 1); - theme->set_constant("scroll_border", "Tree", 4); + theme->set_constant("scroll_border", "Tree", Math::round(4 * scale)); theme->set_constant("scroll_speed", "Tree", 12); theme->set_constant("outline_size", "Tree", 0); theme->set_constant("icon_max_width", "Tree", 0); @@ -803,9 +804,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_stylebox("panel", "ItemList", make_flat_stylebox(style_normal_color)); theme->set_stylebox("focus", "ItemList", focus); - theme->set_constant("h_separation", "ItemList", 4); - theme->set_constant("v_separation", "ItemList", 2); - theme->set_constant("icon_margin", "ItemList", 4); + theme->set_constant("h_separation", "ItemList", Math::round(4 * scale)); + theme->set_constant("v_separation", "ItemList", Math::round(2 * scale)); + theme->set_constant("icon_margin", "ItemList", Math::round(4 * scale)); theme->set_constant("line_separation", "ItemList", Math::round(2 * scale)); theme->set_font("font", "ItemList", Ref<Font>()); @@ -1005,7 +1006,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_disabled_color", "ColorPickerButton", Color(0.9, 0.9, 0.9, 0.3)); theme->set_color("font_outline_color", "ColorPickerButton", Color(1, 1, 1)); - theme->set_constant("h_separation", "ColorPickerButton", Math::round(2 * scale)); + theme->set_constant("h_separation", "ColorPickerButton", Math::round(4 * scale)); theme->set_constant("outline_size", "ColorPickerButton", 0); // ColorPresetButton diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp index e48f744c72..abe1561310 100644 --- a/scene/resources/environment.cpp +++ b/scene/resources/environment.cpp @@ -1446,7 +1446,7 @@ void Environment::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_emission_energy", PROPERTY_HINT_RANGE, "0,1024,0.01,or_greater"), "set_volumetric_fog_emission_energy", "get_volumetric_fog_emission_energy"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_gi_inject", PROPERTY_HINT_RANGE, "0.0,16,0.01,exp"), "set_volumetric_fog_gi_inject", "get_volumetric_fog_gi_inject"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_anisotropy", PROPERTY_HINT_RANGE, "-0.9,0.9,0.01"), "set_volumetric_fog_anisotropy", "get_volumetric_fog_anisotropy"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_length", PROPERTY_HINT_RANGE, "0,1024,0.01,or_greater"), "set_volumetric_fog_length", "get_volumetric_fog_length"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_length", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_volumetric_fog_length", "get_volumetric_fog_length"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_detail_spread", PROPERTY_HINT_EXP_EASING, "positive_only"), "set_volumetric_fog_detail_spread", "get_volumetric_fog_detail_spread"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_ambient_inject", PROPERTY_HINT_RANGE, "0.0,16,0.01,exp"), "set_volumetric_fog_ambient_inject", "get_volumetric_fog_ambient_inject"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_sky_affect", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_volumetric_fog_sky_affect", "get_volumetric_fog_sky_affect"); diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp index 745c71626c..72d38ec8ce 100644 --- a/scene/resources/particle_process_material.cpp +++ b/scene/resources/particle_process_material.cpp @@ -683,11 +683,13 @@ void ParticleProcessMaterial::_update_shader() { code += " pos.z = 0.0;\n"; } code += " // apply linear acceleration\n"; - code += " force += length(VELOCITY) > 0.0 ? normalize(VELOCITY) * tex_linear_accel * mix(linear_accel_min, linear_accel_max, rand_from_seed(alt_seed)) : vec3(0.0);\n"; + code += " float linear_accel_rand = rand_from_seed(alt_seed);\n"; + code += " force += length(VELOCITY) > 0.0 ? normalize(VELOCITY) * tex_linear_accel * mix(linear_accel_min, linear_accel_max, linear_accel_rand) : vec3(0.0);\n"; code += " // apply radial acceleration\n"; code += " vec3 org = EMISSION_TRANSFORM[3].xyz;\n"; code += " vec3 diff = pos - org;\n"; - code += " force += length(diff) > 0.0 ? normalize(diff) * tex_radial_accel * mix(radial_accel_min, radial_accel_max, rand_from_seed(alt_seed)) : vec3(0.0);\n"; + code += " float radial_accel_rand = rand_from_seed(alt_seed);\n"; + code += " force += length(diff) > 0.0 ? normalize(diff) * tex_radial_accel * mix(radial_accel_min, radial_accel_max, radial_accel_rand) : vec3(0.0);\n"; code += " // apply tangential acceleration;\n"; code += " float tangent_accel_val = tex_tangent_accel * mix(tangent_accel_min, tangent_accel_max, rand_from_seed(alt_seed));\n"; if (particle_flags[PARTICLE_FLAG_DISABLE_Z]) { diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index 6430a1302d..a64ae07f05 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -2066,8 +2066,8 @@ void TorusMesh::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inner_radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_inner_radius", "get_inner_radius"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "outer_radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_outer_radius", "get_outer_radius"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "3,128,1"), "set_rings", "get_rings"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "ring_segments", PROPERTY_HINT_RANGE, "3,64,1"), "set_ring_segments", "get_ring_segments"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "3,128,1,or_greater"), "set_rings", "get_rings"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "ring_segments", PROPERTY_HINT_RANGE, "3,64,1,or_greater"), "set_ring_segments", "get_ring_segments"); } void TorusMesh::set_inner_radius(const float p_inner_radius) { diff --git a/servers/rendering/renderer_rd/effects/tone_mapper.cpp b/servers/rendering/renderer_rd/effects/tone_mapper.cpp index 2c537c3a1c..48c6511408 100644 --- a/servers/rendering/renderer_rd/effects/tone_mapper.cpp +++ b/servers/rendering/renderer_rd/effects/tone_mapper.cpp @@ -255,5 +255,5 @@ void ToneMapper::tonemapper(RD::DrawListID p_subpass_draw_list, RID p_source_col RD::get_singleton()->draw_list_bind_uniform_set(p_subpass_draw_list, uniform_set_cache->get_cache(shader, 3, u_color_correction_texture), 3); RD::get_singleton()->draw_list_set_push_constant(p_subpass_draw_list, &tonemap.push_constant, sizeof(TonemapPushConstant)); - RD::get_singleton()->draw_list_draw(p_subpass_draw_list, false, 1u, 0u); + RD::get_singleton()->draw_list_draw(p_subpass_draw_list, false, 1u, 3u); } diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 9c7990d679..2974e9c4a3 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -1010,13 +1010,6 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_DEPTH_TEXTURE) { scene_state.used_depth_texture = true; } - - if (p_color_pass_flags & COLOR_PASS_FLAG_MOTION_VECTORS) { - if ((flags & (INSTANCE_DATA_FLAG_MULTIMESH | INSTANCE_DATA_FLAG_PARTICLES)) == INSTANCE_DATA_FLAG_MULTIMESH && RendererRD::MeshStorage::get_singleton()->_multimesh_enable_motion_vectors(inst->data->base)) { - inst->transforms_uniform_set = mesh_storage->multimesh_get_3d_uniform_set(inst->data->base, scene_shader.default_shader_rd, TRANSFORMS_UNIFORM_SET); - } - } - } else if (p_pass_mode == PASS_MODE_SHADOW || p_pass_mode == PASS_MODE_SHADOW_DP) { if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_PASS_SHADOW) { rl->add_element(surf); diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp index 773ea9098a..0ee76e0aa4 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp @@ -348,7 +348,7 @@ void RendererCanvasRenderRD::free_polygon(PolygonID p_polygon) { //////////////////// -void RendererCanvasRenderRD::_bind_canvas_texture(RD::DrawListID p_draw_list, RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID &r_last_texture, PushConstant &push_constant, Size2 &r_texpixel_size) { +void RendererCanvasRenderRD::_bind_canvas_texture(RD::DrawListID p_draw_list, RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID &r_last_texture, PushConstant &push_constant, Size2 &r_texpixel_size, bool p_texture_is_data) { if (p_texture == RID()) { p_texture = default_canvas_texture; } @@ -363,7 +363,7 @@ void RendererCanvasRenderRD::_bind_canvas_texture(RD::DrawListID p_draw_list, RI bool use_normal; bool use_specular; - bool success = RendererRD::TextureStorage::get_singleton()->canvas_texture_get_uniform_set(p_texture, p_base_filter, p_base_repeat, shader.default_version_rd_shader, CANVAS_TEXTURE_UNIFORM_SET, bool(push_constant.flags & FLAGS_CONVERT_ATTRIBUTES_TO_LINEAR), uniform_set, size, specular_shininess, use_normal, use_specular); + bool success = RendererRD::TextureStorage::get_singleton()->canvas_texture_get_uniform_set(p_texture, p_base_filter, p_base_repeat, shader.default_version_rd_shader, CANVAS_TEXTURE_UNIFORM_SET, bool(push_constant.flags & FLAGS_CONVERT_ATTRIBUTES_TO_LINEAR), uniform_set, size, specular_shininess, use_normal, use_specular, p_texture_is_data); //something odd happened if (!success) { _bind_canvas_texture(p_draw_list, default_canvas_texture, p_base_filter, p_base_repeat, r_last_texture, push_constant, r_texpixel_size); @@ -507,7 +507,7 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_rend //bind textures - _bind_canvas_texture(p_draw_list, rect->texture, current_filter, current_repeat, last_texture, push_constant, texpixel_size); + _bind_canvas_texture(p_draw_list, rect->texture, current_filter, current_repeat, last_texture, push_constant, texpixel_size, bool(rect->flags & CANVAS_RECT_MSDF)); Rect2 src_rect; Rect2 dst_rect; diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h index af8736a445..4c8cbd1c9f 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h @@ -418,7 +418,7 @@ class RendererCanvasRenderRD : public RendererCanvasRender { RID _create_base_uniform_set(RID p_to_render_target, bool p_backbuffer); - inline void _bind_canvas_texture(RD::DrawListID p_draw_list, RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID &r_last_texture, PushConstant &push_constant, Size2 &r_texpixel_size); //recursive, so regular inline used instead. + inline void _bind_canvas_texture(RD::DrawListID p_draw_list, RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID &r_last_texture, PushConstant &push_constant, Size2 &r_texpixel_size, bool p_texture_is_data = false); //recursive, so regular inline used instead. void _render_item(RenderingDevice::DrawListID p_draw_list, RID p_render_target, const Item *p_item, RenderingDevice::FramebufferFormatID p_framebuffer_format, const Transform2D &p_canvas_transform_inverse, Item *¤t_clip, Light *p_lights, PipelineVariants *p_pipeline_variants, bool &r_sdf_used); void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false); diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp index a96893570e..fda341bbc9 100644 --- a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp @@ -1821,14 +1821,16 @@ void MaterialStorage::global_shader_parameters_load_settings(bool p_load_texture if (gvtype >= RS::GLOBAL_VAR_TYPE_SAMPLER2D) { //textire if (!p_load_textures) { - value = RID(); continue; } String path = value; - Ref<Resource> resource = ResourceLoader::load(path); - ERR_CONTINUE(resource.is_null()); - value = resource; + if (path.is_empty()) { + value = RID(); + } else { + Ref<Resource> resource = ResourceLoader::load(path); + value = resource; + } } if (global_shader_uniforms.variables.has(name)) { diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index 67b5cdd291..56f2ea0b0c 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -1289,12 +1289,9 @@ void MeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instances, RS:: multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_MULTIMESH); } -bool MeshStorage::_multimesh_enable_motion_vectors(RID p_multimesh) { - MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); - ERR_FAIL_COND_V(!multimesh, false); - +void MeshStorage::_multimesh_enable_motion_vectors(MultiMesh *multimesh) { if (multimesh->motion_vectors_enabled) { - return false; + return; } multimesh->motion_vectors_enabled = true; @@ -1307,22 +1304,30 @@ bool MeshStorage::_multimesh_enable_motion_vectors(RID p_multimesh) { multimesh->data_cache.append_array(multimesh->data_cache); } - if (multimesh->buffer_set) { + uint32_t buffer_size = multimesh->instances * multimesh->stride_cache * sizeof(float); + uint32_t new_buffer_size = buffer_size * 2; + RID new_buffer = RD::get_singleton()->storage_buffer_create(new_buffer_size); + + if (multimesh->buffer_set && multimesh->data_cache.is_empty()) { + // If the buffer was set but there's no data cached in the CPU, we copy the buffer directly on the GPU. RD::get_singleton()->barrier(); - Vector<uint8_t> buffer_data = RD::get_singleton()->buffer_get_data(multimesh->buffer); - if (!multimesh->data_cache.is_empty()) { - memcpy(buffer_data.ptrw(), multimesh->data_cache.ptr(), buffer_data.size()); - } + RD::get_singleton()->buffer_copy(multimesh->buffer, new_buffer, 0, 0, buffer_size, RD::BARRIER_MASK_NO_BARRIER); + RD::get_singleton()->buffer_copy(multimesh->buffer, new_buffer, 0, buffer_size, buffer_size); + } else if (!multimesh->data_cache.is_empty()) { + // Simply upload the data cached in the CPU, which should already be doubled in size. + ERR_FAIL_COND(multimesh->data_cache.size() * sizeof(float) != size_t(new_buffer_size)); + RD::get_singleton()->buffer_update(new_buffer, 0, new_buffer_size, multimesh->data_cache.ptr()); + } + if (multimesh->buffer.is_valid()) { RD::get_singleton()->free(multimesh->buffer); - uint32_t buffer_size = multimesh->instances * multimesh->stride_cache * sizeof(float) * 2; - multimesh->buffer = RD::get_singleton()->storage_buffer_create(buffer_size); - RD::get_singleton()->buffer_update(multimesh->buffer, 0, buffer_data.size(), buffer_data.ptr(), RD::BARRIER_MASK_NO_BARRIER); - RD::get_singleton()->buffer_update(multimesh->buffer, buffer_data.size(), buffer_data.size(), buffer_data.ptr()); - multimesh->uniform_set_3d = RID(); // Cleared by dependency - return true; } - return false; // Update the transforms uniform set cache + + multimesh->buffer = new_buffer; + multimesh->uniform_set_3d = RID(); // Cleared by dependency. + + // Invalidate any references to the buffer that was released and the uniform set that was pointing to it. + multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_MULTIMESH); } void MeshStorage::_multimesh_get_motion_vectors_offsets(RID p_multimesh, uint32_t &r_current_offset, uint32_t &r_prev_offset) { @@ -1531,6 +1536,12 @@ void MeshStorage::multimesh_instance_set_transform(RID p_multimesh, int p_index, ERR_FAIL_COND(multimesh->xform_format != RS::MULTIMESH_TRANSFORM_3D); _multimesh_make_local(multimesh); + + bool uses_motion_vectors = (RSG::viewport->get_num_viewports_with_motion_vectors() > 0); + if (uses_motion_vectors) { + _multimesh_enable_motion_vectors(multimesh); + } + _multimesh_update_motion_vectors_data_cache(multimesh); { @@ -1749,6 +1760,11 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b ERR_FAIL_COND(!multimesh); ERR_FAIL_COND(p_buffer.size() != (multimesh->instances * (int)multimesh->stride_cache)); + bool uses_motion_vectors = (RSG::viewport->get_num_viewports_with_motion_vectors() > 0); + if (uses_motion_vectors) { + _multimesh_enable_motion_vectors(multimesh); + } + if (multimesh->motion_vectors_enabled) { uint32_t frame = RSG::rasterizer->get_frame_number(); diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h index 4d46a62a76..c23a5b1449 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h @@ -234,6 +234,7 @@ private: MultiMesh *multimesh_dirty_list = nullptr; _FORCE_INLINE_ void _multimesh_make_local(MultiMesh *multimesh) const; + _FORCE_INLINE_ void _multimesh_enable_motion_vectors(MultiMesh *multimesh); _FORCE_INLINE_ void _multimesh_update_motion_vectors_data_cache(MultiMesh *multimesh); _FORCE_INLINE_ void _multimesh_mark_dirty(MultiMesh *multimesh, int p_index, bool p_aabb); _FORCE_INLINE_ void _multimesh_mark_all_dirty(MultiMesh *multimesh, bool p_data, bool p_aabb); @@ -593,7 +594,6 @@ public: virtual AABB multimesh_get_aabb(RID p_multimesh) const override; void _update_dirty_multimeshes(); - bool _multimesh_enable_motion_vectors(RID p_multimesh); void _multimesh_get_motion_vectors_offsets(RID p_multimesh, uint32_t &r_current_offset, uint32_t &r_prev_offset); _FORCE_INLINE_ RS::MultimeshTransformFormat multimesh_get_transform_format(RID p_multimesh) const { diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp index f009318d24..286a9528fc 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp @@ -643,7 +643,7 @@ void TextureStorage::canvas_texture_set_texture_repeat(RID p_canvas_texture, RS: ct->clear_sets(); } -bool TextureStorage::canvas_texture_get_uniform_set(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID p_base_shader, int p_base_set, bool p_use_srgb, RID &r_uniform_set, Size2i &r_size, Color &r_specular_shininess, bool &r_use_normal, bool &r_use_specular) { +bool TextureStorage::canvas_texture_get_uniform_set(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID p_base_shader, int p_base_set, bool p_use_srgb, RID &r_uniform_set, Size2i &r_size, Color &r_specular_shininess, bool &r_use_normal, bool &r_use_specular, bool p_texture_is_data) { MaterialStorage *material_storage = MaterialStorage::get_singleton(); CanvasTexture *ct = nullptr; @@ -690,7 +690,7 @@ bool TextureStorage::canvas_texture_get_uniform_set(RID p_texture, RS::CanvasIte u.append_id(texture_rd_get_default(DEFAULT_RD_TEXTURE_WHITE)); ct->size_cache = Size2i(1, 1); } else { - u.append_id(t->rd_texture_srgb.is_valid() && (p_use_srgb) ? t->rd_texture_srgb : t->rd_texture); + u.append_id(t->rd_texture_srgb.is_valid() && p_use_srgb && !p_texture_is_data ? t->rd_texture_srgb : t->rd_texture); ct->size_cache = Size2i(t->width_2d, t->height_2d); if (t->render_target) { t->render_target->was_used = true; diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.h b/servers/rendering/renderer_rd/storage_rd/texture_storage.h index 2137c5f383..276c8c4ce2 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.h @@ -475,7 +475,7 @@ public: virtual void canvas_texture_set_texture_filter(RID p_item, RS::CanvasItemTextureFilter p_filter) override; virtual void canvas_texture_set_texture_repeat(RID p_item, RS::CanvasItemTextureRepeat p_repeat) override; - bool canvas_texture_get_uniform_set(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID p_base_shader, int p_base_set, bool p_use_srgb, RID &r_uniform_set, Size2i &r_size, Color &r_specular_shininess, bool &r_use_normal, bool &r_use_specular); + bool canvas_texture_get_uniform_set(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID p_base_shader, int p_base_set, bool p_use_srgb, RID &r_uniform_set, Size2i &r_size, Color &r_specular_shininess, bool &r_use_normal, bool &r_use_specular, bool p_texture_is_data); /* Texture API */ diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp index 75371de814..5880bf3951 100644 --- a/servers/rendering/renderer_viewport.cpp +++ b/servers/rendering/renderer_viewport.cpp @@ -1194,6 +1194,7 @@ void RendererViewport::viewport_set_use_taa(RID p_viewport, bool p_use_taa) { return; } viewport->use_taa = p_use_taa; + num_viewports_with_motion_vectors += p_use_taa ? 1 : -1; _configure_3d_render_buffers(viewport); } @@ -1378,6 +1379,10 @@ bool RendererViewport::free(RID p_rid) { RendererSceneOcclusionCull::get_singleton()->remove_buffer(p_rid); } + if (viewport->use_taa) { + num_viewports_with_motion_vectors--; + } + viewport_owner.free(p_rid); return true; @@ -1433,6 +1438,10 @@ int RendererViewport::get_total_draw_calls_used() const { return total_draw_calls_used; } +int RendererViewport::get_num_viewports_with_motion_vectors() const { + return num_viewports_with_motion_vectors; +} + RendererViewport::RendererViewport() { occlusion_rays_per_thread = GLOBAL_GET("rendering/occlusion_culling/occlusion_rays_per_thread"); } diff --git a/servers/rendering/renderer_viewport.h b/servers/rendering/renderer_viewport.h index 92c5929796..3bfb1afd51 100644 --- a/servers/rendering/renderer_viewport.h +++ b/servers/rendering/renderer_viewport.h @@ -198,6 +198,8 @@ public: int total_vertices_drawn = 0; int total_draw_calls_used = 0; + int num_viewports_with_motion_vectors = 0; + private: Vector<Viewport *> _sort_active_viewports(); void _viewport_set_size(Viewport *p_viewport, int p_width, int p_height, uint32_t p_view_count); @@ -302,6 +304,7 @@ public: int get_total_objects_drawn() const; int get_total_primitives_drawn() const; int get_total_draw_calls_used() const; + int get_num_viewports_with_motion_vectors() const; // Workaround for setting this on thread. void call_set_vsync_mode(DisplayServer::VSyncMode p_mode, DisplayServer::WindowID p_window); diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h index 30d9b1c7b7..1ade1b25c4 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -840,6 +840,7 @@ public: virtual bool uniform_set_is_valid(RID p_uniform_set) = 0; virtual void uniform_set_set_invalidation_callback(RID p_uniform_set, InvalidationCallback p_callback, void *p_userdata) = 0; + virtual Error buffer_copy(RID p_src_buffer, RID p_dst_buffer, uint32_t p_src_offset, uint32_t p_dst_offset, uint32_t p_size, BitField<BarrierMask> p_post_barrier = BARRIER_MASK_ALL_BARRIERS) = 0; virtual Error buffer_update(RID p_buffer, uint32_t p_offset, uint32_t p_size, const void *p_data, BitField<BarrierMask> p_post_barrier = BARRIER_MASK_ALL_BARRIERS) = 0; virtual Error buffer_clear(RID p_buffer, uint32_t p_offset, uint32_t p_size, BitField<BarrierMask> p_post_barrier = BARRIER_MASK_ALL_BARRIERS) = 0; virtual Vector<uint8_t> buffer_get_data(RID p_buffer, uint32_t p_offset = 0, uint32_t p_size = 0) = 0; // This causes stall, only use to retrieve large buffers for saving. diff --git a/tests/core/io/test_json.h b/tests/core/io/test_json.h index 73c7ac7a2e..bf2ed42740 100644 --- a/tests/core/io/test_json.h +++ b/tests/core/io/test_json.h @@ -186,20 +186,21 @@ TEST_CASE("[JSON] Parsing escape sequences") { } SUBCASE("Valid unicode escape sequences") { - String json_string = "\"\\u0000\""; + String json_string = "\"\\u0020\""; json.parse(json_string); CHECK_MESSAGE( json.get_error_line() == 0, - vformat("Parsing valid unicode escape sequence with value `0000` as JSON should parse successfully.")); + vformat("Parsing valid unicode escape sequence with value `0020` as JSON should parse successfully.")); String json_value = json.get_data(); CHECK_MESSAGE( - json_value == "\0", - vformat("Parsing valid unicode escape sequence with value `0000` as JSON should return the expected value.")); + json_value == " ", + vformat("Parsing valid unicode escape sequence with value `0020` as JSON should return the expected value.")); } SUBCASE("Invalid escape sequences") { + ERR_PRINT_OFF for (char32_t i = 0; i < 128; i++) { bool skip = false; for (int j = 0; j < valid_escapes.size(); j++) { @@ -228,6 +229,7 @@ TEST_CASE("[JSON] Parsing escape sequences") { err == ERR_PARSE_ERROR, vformat("Parsing invalid escape sequence with ASCII value `%d` as JSON should fail to parse with ERR_PARSE_ERROR.", i)); } + ERR_PRINT_ON } } } // namespace TestJSON diff --git a/tests/core/object/test_class_db.h b/tests/core/object/test_class_db.h index 3f091fd2fe..5f7de11c71 100644 --- a/tests/core/object/test_class_db.h +++ b/tests/core/object/test_class_db.h @@ -409,9 +409,6 @@ void validate_method(const Context &p_context, const ExposedClass &p_class, cons if (p_method.return_type.name != StringName()) { const ExposedClass *return_class = p_context.find_exposed_class(p_method.return_type); if (return_class) { - TEST_COND(return_class->is_singleton, - "Method return type is a singleton: '", p_class.name, ".", p_method.name, "'."); - if (p_class.api_type == ClassDB::API_CORE) { TEST_COND(return_class->api_type == ClassDB::API_EDITOR, "Method '", p_class.name, ".", p_method.name, "' has return type '", return_class->name, diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h index fc1e0590fc..659fb003d3 100644 --- a/tests/core/string/test_string.h +++ b/tests/core/string/test_string.h @@ -1609,6 +1609,11 @@ TEST_CASE("[String] Repeat") { CHECK(t == s); } +TEST_CASE("[String] Reverse") { + String s = "Abcd"; + CHECK(s.reverse() == "dcbA"); +} + TEST_CASE("[String] SHA1/SHA256/MD5") { String s = "Godot"; String sha1 = "a1e91f39b9fce6a9998b14bdbe2aa2b39dc2d201"; diff --git a/tests/scene/test_packed_scene.h b/tests/scene/test_packed_scene.h index 9e525ab5bf..3517aba31f 100644 --- a/tests/scene/test_packed_scene.h +++ b/tests/scene/test_packed_scene.h @@ -109,6 +109,47 @@ TEST_CASE("[PackedScene] Instantiate Packed Scene") { memdelete(instance); } +TEST_CASE("[PackedScene] Instantiate Packed Scene With Children") { + // Create a scene to pack. + Node *scene = memnew(Node); + scene->set_name("TestScene"); + + // Add persisting child nodes to the scene. + Node *child1 = memnew(Node); + child1->set_name("Child1"); + scene->add_child(child1); + child1->set_owner(scene); + + Node *child2 = memnew(Node); + child2->set_name("Child2"); + scene->add_child(child2); + child2->set_owner(scene); + + // Add non persisting child node to the scene. + Node *child3 = memnew(Node); + child3->set_name("Child3"); + scene->add_child(child3); + + // Pack the scene. + PackedScene packed_scene; + packed_scene.pack(scene); + + // Instantiate the packed scene. + Node *instance = packed_scene.instantiate(); + CHECK(instance != nullptr); + CHECK(instance->get_name() == "TestScene"); + + // Validate the child nodes of the instantiated scene. + CHECK(instance->get_child_count() == 2); + CHECK(instance->get_child(0)->get_name() == "Child1"); + CHECK(instance->get_child(1)->get_name() == "Child2"); + CHECK(instance->get_child(0)->get_owner() == instance); + CHECK(instance->get_child(1)->get_owner() == instance); + + memdelete(scene); + memdelete(instance); +} + } // namespace TestPackedScene #endif // TEST_PACKED_SCENE_H diff --git a/tests/scene/test_window.h b/tests/scene/test_window.h new file mode 100644 index 0000000000..e0c55101de --- /dev/null +++ b/tests/scene/test_window.h @@ -0,0 +1,96 @@ +/**************************************************************************/ +/* test_window.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_WINDOW_H +#define TEST_WINDOW_H + +#include "scene/gui/control.h" +#include "scene/main/window.h" + +#include "tests/test_macros.h" + +namespace TestWindow { + +class NotificationControl : public Control { + GDCLASS(NotificationControl, Control); + +protected: + void _notification(int p_what) { + switch (p_what) { + case NOTIFICATION_MOUSE_ENTER: { + mouse_over = true; + } break; + + case NOTIFICATION_MOUSE_EXIT: { + mouse_over = false; + } break; + } + } + +public: + bool mouse_over = false; +}; + +TEST_CASE("[SceneTree][Window]") { + Window *root = SceneTree::get_singleton()->get_root(); + + SUBCASE("Control-mouse-over within Window-black bars should not happen") { + Window *w = memnew(Window); + root->add_child(w); + w->set_size(Size2i(400, 200)); + w->set_position(Size2i(0, 0)); + w->set_content_scale_size(Size2i(200, 200)); + w->set_content_scale_mode(Window::CONTENT_SCALE_MODE_CANVAS_ITEMS); + w->set_content_scale_aspect(Window::CONTENT_SCALE_ASPECT_KEEP); + NotificationControl *c = memnew(NotificationControl); + w->add_child(c); + c->set_size(Size2i(100, 100)); + c->set_position(Size2i(-50, -50)); + + CHECK_FALSE(c->mouse_over); + SEND_GUI_MOUSE_MOTION_EVENT(Point2i(110, 10), MouseButtonMask::NONE, Key::NONE); + CHECK(c->mouse_over); + SEND_GUI_MOUSE_MOTION_EVENT(Point2i(90, 10), MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(c->mouse_over); // GH-80011 + + /* TODO: + SEND_GUI_MOUSE_BUTTON_EVENT(Point2i(90, 10), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); + SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(Point2i(90, 10), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); + CHECK(Control was not pressed); + */ + + memdelete(c); + memdelete(w); + } +} + +} // namespace TestWindow + +#endif // TEST_WINDOW_H diff --git a/tests/servers/rendering/test_shader_preprocessor.h b/tests/servers/rendering/test_shader_preprocessor.h index 41a009ccf5..d65eb522e8 100644 --- a/tests/servers/rendering/test_shader_preprocessor.h +++ b/tests/servers/rendering/test_shader_preprocessor.h @@ -52,8 +52,7 @@ bool is_variable_char(unsigned char c) { } bool is_operator_char(unsigned char c) { - static const std::string operators = "<>=+-*/"; - return operators.find(c) != std::string::npos; + return (c == '*') || (c == '+') || (c == '-') || (c == '/') || ((c >= '<') && (c <= '>')); } // Remove unnecessary spaces from a line. diff --git a/tests/servers/test_navigation_server_3d.h b/tests/servers/test_navigation_server_3d.h index 8ea69ec67c..a116559cb2 100644 --- a/tests/servers/test_navigation_server_3d.h +++ b/tests/servers/test_navigation_server_3d.h @@ -423,6 +423,7 @@ TEST_SUITE("[Navigation]") { navigation_server->free(map); } +#ifndef DISABLE_DEPRECATED // This test case uses only public APIs on purpose - other test cases use simplified baking. // FIXME: Remove once deprecated `region_bake_navigation_mesh()` is removed. TEST_CASE("[NavigationServer3D][SceneTree][DEPRECATED] Server should be able to bake map correctly") { @@ -470,6 +471,7 @@ TEST_SUITE("[Navigation]") { memdelete(mesh_instance); memdelete(node_3d); } +#endif // DISABLE_DEPRECATED // This test case uses only public APIs on purpose - other test cases use simplified baking. TEST_CASE("[NavigationServer3D][SceneTree] Server should be able to bake map correctly") { diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 5ca03a20af..f1e348345b 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -115,6 +115,7 @@ #include "tests/scene/test_theme.h" #include "tests/scene/test_viewport.h" #include "tests/scene/test_visual_shader.h" +#include "tests/scene/test_window.h" #include "tests/servers/rendering/test_shader_preprocessor.h" #include "tests/servers/test_navigation_server_2d.h" #include "tests/servers/test_navigation_server_3d.h" |