diff options
223 files changed, 5685 insertions, 1740 deletions
diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 1fe34cb4fd..9503bd2575 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -236,8 +236,12 @@ Vector<String> OS::get_system_fonts() const { return ::OS::get_singleton()->get_system_fonts(); } -String OS::get_system_font_path(const String &p_font_name, bool p_bold, bool p_italic) const { - return ::OS::get_singleton()->get_system_font_path(p_font_name, p_bold, p_italic); +String OS::get_system_font_path(const String &p_font_name, int p_weight, int p_stretch, bool p_italic) const { + return ::OS::get_singleton()->get_system_font_path(p_font_name, p_weight, p_stretch, p_italic); +} + +Vector<String> OS::get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale, const String &p_script, int p_weight, int p_stretch, bool p_italic) const { + return ::OS::get_singleton()->get_system_font_path_for_text(p_font_name, p_text, p_locale, p_script, p_weight, p_stretch, p_italic); } String OS::get_executable_path() const { @@ -532,7 +536,8 @@ void OS::_bind_methods() { ClassDB::bind_method(D_METHOD("get_processor_name"), &OS::get_processor_name); ClassDB::bind_method(D_METHOD("get_system_fonts"), &OS::get_system_fonts); - ClassDB::bind_method(D_METHOD("get_system_font_path", "font_name", "bold", "italic"), &OS::get_system_font_path, DEFVAL(false), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_system_font_path", "font_name", "weight", "stretch", "italic"), &OS::get_system_font_path, DEFVAL(400), DEFVAL(100), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_system_font_path_for_text", "font_name", "text", "locale", "script", "weight", "stretch", "italic"), &OS::get_system_font_path_for_text, DEFVAL(String()), DEFVAL(String()), DEFVAL(400), DEFVAL(100), DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_executable_path"), &OS::get_executable_path); ClassDB::bind_method(D_METHOD("read_string_from_stdin", "block"), &OS::read_string_from_stdin, DEFVAL(true)); ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL(Array()), DEFVAL(false), DEFVAL(false)); diff --git a/core/core_bind.h b/core/core_bind.h index 748ecb4929..c863a8094c 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -170,7 +170,8 @@ public: void crash(const String &p_message); Vector<String> get_system_fonts() const; - String get_system_font_path(const String &p_font_name, bool p_bold = false, bool p_italic = false) const; + String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const; + Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const; String get_executable_path() const; String read_string_from_stdin(bool p_block = true); int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = Array(), bool p_read_stderr = false, bool p_open_console = false); diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 628740ed32..e0772cd43d 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -637,7 +637,6 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_SCRIPT_VARIABLE); BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_STORE_IF_NULL); - BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_ANIMATE_AS_TRIGGER); BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED); BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE); BIND_CORE_ENUM_CONSTANT(PROPERTY_USAGE_CLASS_IS_ENUM); diff --git a/core/extension/native_extension.cpp b/core/extension/native_extension.cpp index 37967cdb48..e7e350687b 100644 --- a/core/extension/native_extension.cpp +++ b/core/extension/native_extension.cpp @@ -30,7 +30,7 @@ #include "native_extension.h" #include "core/config/project_settings.h" -#include "core/io/config_file.h" +#include "core/io/dir_access.h" #include "core/object/class_db.h" #include "core/object/method_bind.h" #include "core/os/os.h" @@ -39,6 +39,111 @@ String NativeExtension::get_extension_list_config_file() { return ProjectSettings::get_singleton()->get_project_data_path().path_join("extension_list.cfg"); } +String NativeExtension::find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags) { + // First, check the explicit libraries. + if (p_config->has_section("libraries")) { + List<String> libraries; + p_config->get_section_keys("libraries", &libraries); + + // Iterate the libraries, finding the best matching tags. + String best_library_path; + Vector<String> best_library_tags; + for (const String &E : libraries) { + Vector<String> tags = E.split("."); + bool all_tags_met = true; + for (int i = 0; i < tags.size(); i++) { + String tag = tags[i].strip_edges(); + if (!p_has_feature(tag)) { + all_tags_met = false; + break; + } + } + + if (all_tags_met && tags.size() > best_library_tags.size()) { + best_library_path = p_config->get_value("libraries", E); + best_library_tags = tags; + } + } + + if (!best_library_path.is_empty()) { + if (best_library_path.is_relative_path()) { + best_library_path = p_path.get_base_dir().path_join(best_library_path); + } + if (r_tags != nullptr) { + r_tags->append_array(best_library_tags); + } + return best_library_path; + } + } + + // Second, try to autodetect + String autodetect_library_prefix; + if (p_config->has_section_key("configuration", "autodetect_library_prefix")) { + autodetect_library_prefix = p_config->get_value("configuration", "autodetect_library_prefix"); + } + if (!autodetect_library_prefix.is_empty()) { + String autodetect_path = autodetect_library_prefix; + if (autodetect_path.is_relative_path()) { + autodetect_path = p_path.get_base_dir().path_join(autodetect_path); + } + + // Find the folder and file parts of the prefix. + String folder; + String file_prefix; + if (DirAccess::dir_exists_absolute(autodetect_path)) { + folder = autodetect_path; + } else if (DirAccess::dir_exists_absolute(autodetect_path.get_base_dir())) { + folder = autodetect_path.get_base_dir(); + file_prefix = autodetect_path.get_file(); + } else { + ERR_FAIL_V_MSG(String(), vformat("Error in extension: %s. Could not find folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix)); + } + + // Open the folder. + Ref<DirAccess> dir = DirAccess::open(folder); + ERR_FAIL_COND_V_MSG(!dir.is_valid(), String(), vformat("Error in extension: %s. Could not open folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix)); + + // Iterate the files and check the prefixes, finding the best matching file. + String best_file; + Vector<String> best_file_tags; + dir->list_dir_begin(); + String file_name = dir->_get_next(); + while (file_name != "") { + if (!dir->current_is_dir() && file_name.begins_with(file_prefix)) { + // Check if the files matches all requested feature tags. + String tags_str = file_name.trim_prefix(file_prefix); + tags_str = tags_str.trim_suffix(tags_str.get_extension()); + + Vector<String> tags = tags_str.split(".", false); + bool all_tags_met = true; + for (int i = 0; i < tags.size(); i++) { + String tag = tags[i].strip_edges(); + if (!p_has_feature(tag)) { + all_tags_met = false; + break; + } + } + + // If all tags are found in the feature list, and we found more tags than before, use this file. + if (all_tags_met && tags.size() > best_file_tags.size()) { + best_file_tags = tags; + best_file = file_name; + } + } + file_name = dir->_get_next(); + } + + if (!best_file.is_empty()) { + String library_path = folder.path_join(best_file); + if (r_tags != nullptr) { + r_tags->append_array(best_file_tags); + } + return library_path; + } + } + return String(); +} + class NativeExtensionMethodBind : public MethodBind { GDNativeExtensionClassMethodCall call_func; GDNativeExtensionClassMethodPtrCall ptrcall_func; @@ -415,28 +520,7 @@ Ref<Resource> NativeExtensionResourceLoader::load(const String &p_path, const St String entry_symbol = config->get_value("configuration", "entry_symbol"); - List<String> libraries; - - config->get_section_keys("libraries", &libraries); - - String library_path; - - for (const String &E : libraries) { - Vector<String> tags = E.split("."); - bool all_tags_met = true; - for (int i = 0; i < tags.size(); i++) { - String tag = tags[i].strip_edges(); - if (!OS::get_singleton()->has_feature(tag)) { - all_tags_met = false; - break; - } - } - - if (all_tags_met) { - library_path = config->get_value("libraries", E); - break; - } - } + String library_path = NativeExtension::find_extension_library(p_path, config, [](String p_feature) { return OS::get_singleton()->has_feature(p_feature); }); if (library_path.is_empty()) { if (r_error) { diff --git a/core/extension/native_extension.h b/core/extension/native_extension.h index ca6200cd1e..77654cf0a8 100644 --- a/core/extension/native_extension.h +++ b/core/extension/native_extension.h @@ -31,7 +31,10 @@ #ifndef NATIVE_EXTENSION_H #define NATIVE_EXTENSION_H +#include <functional> + #include "core/extension/gdnative_interface.h" +#include "core/io/config_file.h" #include "core/io/resource_loader.h" #include "core/object/ref_counted.h" @@ -65,6 +68,7 @@ protected: public: static String get_extension_list_config_file(); + static String find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags = nullptr); Error open_library(const String &p_path, const String &p_entry_symbol); void close_library(); diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index cb25564342..0ceb300f97 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -690,7 +690,7 @@ void FileAccess::store_var(const Variant &p_var, bool p_full_objects) { _store_buffer(buff); } -Vector<uint8_t> FileAccess::get_file_as_array(const String &p_path, Error *r_error) { +Vector<uint8_t> FileAccess::get_file_as_bytes(const String &p_path, Error *r_error) { Ref<FileAccess> f = FileAccess::open(p_path, READ, r_error); if (f.is_null()) { if (r_error) { // if error requested, do not throw error @@ -706,7 +706,7 @@ Vector<uint8_t> FileAccess::get_file_as_array(const String &p_path, Error *r_err String FileAccess::get_file_as_string(const String &p_path, Error *r_error) { Error err; - Vector<uint8_t> array = get_file_as_array(p_path, &err); + Vector<uint8_t> array = get_file_as_bytes(p_path, &err); if (r_error) { *r_error = err; } @@ -810,6 +810,9 @@ void FileAccess::_bind_methods() { ClassDB::bind_static_method("FileAccess", D_METHOD("open_compressed", "path", "mode_flags", "compression_mode"), &FileAccess::open_compressed, DEFVAL(0)); ClassDB::bind_static_method("FileAccess", D_METHOD("get_open_error"), &FileAccess::get_open_error); + ClassDB::bind_static_method("FileAccess", D_METHOD("get_file_as_bytes", "path"), &FileAccess::_get_file_as_bytes); + ClassDB::bind_static_method("FileAccess", D_METHOD("get_file_as_string", "path"), &FileAccess::_get_file_as_string); + ClassDB::bind_method(D_METHOD("flush"), &FileAccess::flush); ClassDB::bind_method(D_METHOD("get_path"), &FileAccess::get_path); ClassDB::bind_method(D_METHOD("get_path_absolute"), &FileAccess::get_path_absolute); diff --git a/core/io/file_access.h b/core/io/file_access.h index 8ca44306a0..54a0235333 100644 --- a/core/io/file_access.h +++ b/core/io/file_access.h @@ -192,9 +192,12 @@ public: static String get_sha256(const String &p_file); static String get_multiple_md5(const Vector<String> &p_file); - static Vector<uint8_t> get_file_as_array(const String &p_path, Error *r_error = nullptr); + static Vector<uint8_t> get_file_as_bytes(const String &p_path, Error *r_error = nullptr); static String get_file_as_string(const String &p_path, Error *r_error = nullptr); + static PackedByteArray _get_file_as_bytes(const String &p_path) { return get_file_as_bytes(p_path); } + static String _get_file_as_string(const String &p_path) { return get_file_as_string(p_path); }; + template <class T> static void make_default(AccessType p_access) { create_func[p_access] = _create_builtin<T>; diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp index 0118b4c6af..0556f45b0c 100644 --- a/core/io/pck_packer.cpp +++ b/core/io/pck_packer.cpp @@ -120,7 +120,7 @@ Error PCKPacker::add_file(const String &p_file, const String &p_src, bool p_encr pf.ofs = ofs; pf.size = f->get_length(); - Vector<uint8_t> data = FileAccess::get_file_as_array(p_src); + Vector<uint8_t> data = FileAccess::get_file_as_bytes(p_src); { unsigned char hash[16]; CryptoCore::md5(data.ptr(), data.size(), hash); diff --git a/core/object/object.cpp b/core/object/object.cpp index d27e0d7621..105f9560d6 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -1473,6 +1473,8 @@ void Object::_bind_methods() { ClassDB::bind_method(D_METHOD("get_indexed", "property_path"), &Object::_get_indexed_bind); ClassDB::bind_method(D_METHOD("get_property_list"), &Object::_get_property_list_bind); ClassDB::bind_method(D_METHOD("get_method_list"), &Object::_get_method_list_bind); + ClassDB::bind_method(D_METHOD("property_can_revert", "property"), &Object::property_can_revert); + ClassDB::bind_method(D_METHOD("property_get_revert", "property"), &Object::property_get_revert); ClassDB::bind_method(D_METHOD("notification", "what", "reversed"), &Object::notification, DEFVAL(false)); ClassDB::bind_method(D_METHOD("to_string"), &Object::to_string); ClassDB::bind_method(D_METHOD("get_instance_id"), &Object::get_instance_id); diff --git a/core/object/object.h b/core/object/object.h index 9416eb7762..3ad8391dd6 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -113,22 +113,21 @@ enum PropertyUsageFlags { PROPERTY_USAGE_RESTART_IF_CHANGED = 1 << 11, PROPERTY_USAGE_SCRIPT_VARIABLE = 1 << 12, PROPERTY_USAGE_STORE_IF_NULL = 1 << 13, - PROPERTY_USAGE_ANIMATE_AS_TRIGGER = 1 << 14, - PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED = 1 << 15, - PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE = 1 << 16, - PROPERTY_USAGE_CLASS_IS_ENUM = 1 << 17, - PROPERTY_USAGE_NIL_IS_VARIANT = 1 << 18, - PROPERTY_USAGE_INTERNAL = 1 << 19, - PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE = 1 << 20, // If the object is duplicated also this property will be duplicated. - PROPERTY_USAGE_HIGH_END_GFX = 1 << 21, - PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT = 1 << 22, - PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT = 1 << 23, - PROPERTY_USAGE_KEYING_INCREMENTS = 1 << 24, // Used in inspector to increment property when keyed in animation player. - PROPERTY_USAGE_DEFERRED_SET_RESOURCE = 1 << 25, // when loading, the resource for this property can be set at the end of loading. - PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT = 1 << 26, // For Object properties, instantiate them when creating in editor. - PROPERTY_USAGE_EDITOR_BASIC_SETTING = 1 << 27, //for project or editor settings, show when basic settings are selected. - PROPERTY_USAGE_READ_ONLY = 1 << 28, // Mark a property as read-only in the inspector. - PROPERTY_USAGE_ARRAY = 1 << 29, // Used in the inspector to group properties as elements of an array. + PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED = 1 << 14, + PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE = 1 << 15, + PROPERTY_USAGE_CLASS_IS_ENUM = 1 << 16, + PROPERTY_USAGE_NIL_IS_VARIANT = 1 << 17, + PROPERTY_USAGE_INTERNAL = 1 << 18, + PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE = 1 << 19, // If the object is duplicated also this property will be duplicated. + PROPERTY_USAGE_HIGH_END_GFX = 1 << 20, + PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT = 1 << 21, + PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT = 1 << 22, + PROPERTY_USAGE_KEYING_INCREMENTS = 1 << 23, // Used in inspector to increment property when keyed in animation player. + PROPERTY_USAGE_DEFERRED_SET_RESOURCE = 1 << 24, // when loading, the resource for this property can be set at the end of loading. + PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT = 1 << 25, // For Object properties, instantiate them when creating in editor. + PROPERTY_USAGE_EDITOR_BASIC_SETTING = 1 << 26, //for project or editor settings, show when basic settings are selected. + PROPERTY_USAGE_READ_ONLY = 1 << 27, // Mark a property as read-only in the inspector. + PROPERTY_USAGE_ARRAY = 1 << 28, // Used in the inspector to group properties as elements of an array. PROPERTY_USAGE_DEFAULT = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR, PROPERTY_USAGE_DEFAULT_INTL = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNATIONALIZED, diff --git a/core/os/os.cpp b/core/os/os.cpp index bbb2a94fe7..055385579f 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -353,20 +353,26 @@ bool OS::has_feature(const String &p_feature) { if (p_feature == "debug") { return true; } +#endif // DEBUG_ENABLED + +#ifdef TOOLS_ENABLED + if (p_feature == "editor") { + return true; + } #else - if (p_feature == "release") { + if (p_feature == "template") { return true; } -#endif -#ifdef TOOLS_ENABLED - if (p_feature == "editor") { +#ifdef DEBUG_ENABLED + if (p_feature == "template_debug") { return true; } #else - if (p_feature == "standalone") { + if (p_feature == "template_release" || p_feature == "release") { return true; } -#endif +#endif // DEBUG_ENABLED +#endif // TOOLS_ENABLED if (sizeof(void *) == 8 && p_feature == "64") { return true; diff --git a/core/os/os.h b/core/os/os.h index af7b40f3ec..72a91f318a 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -150,7 +150,8 @@ public: virtual int get_low_processor_usage_mode_sleep_usec() const; virtual Vector<String> get_system_fonts() const { return Vector<String>(); }; - virtual String get_system_font_path(const String &p_font_name, bool p_bold = false, bool p_italic = false) const { return String(); }; + virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return String(); }; + virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return Vector<String>(); }; virtual String get_executable_path() const; virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) = 0; virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) = 0; diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index c1166a0a14..8be1cda37c 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -940,7 +940,7 @@ bool Variant::is_zero() const { return reinterpret_cast<const Signal *>(_data._mem)->is_null(); } case STRING_NAME: { - return *reinterpret_cast<const StringName *>(_data._mem) != StringName(); + return *reinterpret_cast<const StringName *>(_data._mem) == StringName(); } case NODE_PATH: { return reinterpret_cast<const NodePath *>(_data._mem)->is_empty(); diff --git a/core/variant/variant_op.h b/core/variant/variant_op.h index ec1ce67445..34858540ec 100644 --- a/core/variant/variant_op.h +++ b/core/variant/variant_op.h @@ -890,10 +890,12 @@ public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { const String &a = *VariantGetInternalPtr<String>::get_ptr(&p_left); *r_ret = do_mod(a, &r_valid); - r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - *VariantGetInternalPtr<String>::get_ptr(r_ret) = do_mod(*VariantGetInternalPtr<String>::get_ptr(left), nullptr); + bool valid = true; + String result = do_mod(*VariantGetInternalPtr<String>::get_ptr(left), &valid); + ERR_FAIL_COND_MSG(!valid, result); + *VariantGetInternalPtr<String>::get_ptr(r_ret) = result; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { PtrToArg<String>::encode(do_mod(PtrToArg<String>::convert(left), nullptr), r_ret); @@ -913,10 +915,12 @@ public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { const String &a = *VariantGetInternalPtr<String>::get_ptr(&p_left); *r_ret = do_mod(a, *VariantGetInternalPtr<Array>::get_ptr(&p_right), &r_valid); - r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - *VariantGetInternalPtr<String>::get_ptr(r_ret) = do_mod(*VariantGetInternalPtr<String>::get_ptr(left), *VariantGetInternalPtr<Array>::get_ptr(right), nullptr); + bool valid = true; + String result = do_mod(*VariantGetInternalPtr<String>::get_ptr(left), *VariantGetInternalPtr<Array>::get_ptr(right), &valid); + ERR_FAIL_COND_MSG(!valid, result); + *VariantGetInternalPtr<String>::get_ptr(r_ret) = result; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { PtrToArg<String>::encode(do_mod(PtrToArg<String>::convert(left), PtrToArg<Array>::convert(right), nullptr), r_ret); @@ -939,10 +943,12 @@ public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { const String &a = *VariantGetInternalPtr<String>::get_ptr(&p_left); *r_ret = do_mod(a, p_right.get_validated_object(), &r_valid); - r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - *VariantGetInternalPtr<String>::get_ptr(r_ret) = do_mod(*VariantGetInternalPtr<String>::get_ptr(left), right->get_validated_object(), nullptr); + bool valid = true; + String result = do_mod(*VariantGetInternalPtr<String>::get_ptr(left), right->get_validated_object(), &valid); + ERR_FAIL_COND_MSG(!valid, result); + *VariantGetInternalPtr<String>::get_ptr(r_ret) = result; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { PtrToArg<String>::encode(do_mod(PtrToArg<String>::convert(left), PtrToArg<Object *>::convert(right), nullptr), r_ret); @@ -965,10 +971,12 @@ public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { const String &a = *VariantGetInternalPtr<String>::get_ptr(&p_left); *r_ret = do_mod(a, *VariantGetInternalPtr<T>::get_ptr(&p_right), &r_valid); - r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - *VariantGetInternalPtr<String>::get_ptr(r_ret) = do_mod(*VariantGetInternalPtr<String>::get_ptr(left), *VariantGetInternalPtr<T>::get_ptr(right), nullptr); + bool valid = true; + String result = do_mod(*VariantGetInternalPtr<String>::get_ptr(left), *VariantGetInternalPtr<T>::get_ptr(right), &valid); + ERR_FAIL_COND_MSG(!valid, result); + *VariantGetInternalPtr<String>::get_ptr(r_ret) = result; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { PtrToArg<String>::encode(do_mod(PtrToArg<String>::convert(left), PtrToArg<T>::convert(right), nullptr), r_ret); diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index 17d41ca95e..9f500dbf5e 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -35,37 +35,76 @@ #include "core/os/keyboard.h" #include "core/string/string_buffer.h" -char32_t VariantParser::StreamFile::get_char() { - return f->get_8(); +char32_t VariantParser::Stream::get_char() { + // is within buffer? + if (readahead_pointer < readahead_filled) { + return readahead_buffer[readahead_pointer++]; + } + + // attempt to readahead + readahead_filled = _read_buffer(readahead_buffer, READAHEAD_SIZE); + if (readahead_filled) { + readahead_pointer = 0; + } else { + // EOF + readahead_pointer = 1; + eof = true; + return 0; + } + return get_char(); } bool VariantParser::StreamFile::is_utf8() const { return true; } -bool VariantParser::StreamFile::is_eof() const { - return f->eof_reached(); -} +uint32_t VariantParser::StreamFile::_read_buffer(char32_t *p_buffer, uint32_t p_num_chars) { + // The buffer is assumed to include at least one character (for null terminator) + ERR_FAIL_COND_V(!p_num_chars, 0); -char32_t VariantParser::StreamString::get_char() { - if (pos > s.length()) { - return 0; - } else if (pos == s.length()) { - // You need to try to read again when you have reached the end for EOF to be reported, - // so this works the same as files (like StreamFile does) - pos++; - return 0; - } else { - return s[pos++]; + uint8_t *temp = (uint8_t *)alloca(p_num_chars); + uint64_t num_read = f->get_buffer(temp, p_num_chars); + ERR_FAIL_COND_V(num_read == UINT64_MAX, 0); + + // translate to wchar + for (uint32_t n = 0; n < num_read; n++) { + p_buffer[n] = temp[n]; } + + // could be less than p_num_chars, or zero + return num_read; } bool VariantParser::StreamString::is_utf8() const { return false; } -bool VariantParser::StreamString::is_eof() const { - return pos > s.length(); +uint32_t VariantParser::StreamString::_read_buffer(char32_t *p_buffer, uint32_t p_num_chars) { + // The buffer is assumed to include at least one character (for null terminator) + ERR_FAIL_COND_V(!p_num_chars, 0); + + int available = MAX(s.length() - pos, 0); + if (available >= (int)p_num_chars) { + const char32_t *src = s.ptr(); + src += pos; + memcpy(p_buffer, src, p_num_chars * sizeof(char32_t)); + pos += p_num_chars; + + return p_num_chars; + } + + // going to reach EOF + if (available) { + const char32_t *src = s.ptr(); + src += pos; + memcpy(p_buffer, src, available * sizeof(char32_t)); + pos += available; + } + + // add a zero + p_buffer[available] = 0; + + return available; } ///////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/variant/variant_parser.h b/core/variant/variant_parser.h index 56b484c8bc..6b1d095ab5 100644 --- a/core/variant/variant_parser.h +++ b/core/variant/variant_parser.h @@ -38,34 +38,50 @@ class VariantParser { public: struct Stream { - virtual char32_t get_char() = 0; - virtual bool is_utf8() const = 0; - virtual bool is_eof() const = 0; + private: + enum { READAHEAD_SIZE = 2048 }; + char32_t readahead_buffer[READAHEAD_SIZE]; + uint32_t readahead_pointer = 0; + uint32_t readahead_filled = 0; + bool eof = false; + + protected: + virtual uint32_t _read_buffer(char32_t *p_buffer, uint32_t p_num_chars) = 0; + public: char32_t saved = 0; + char32_t get_char(); + virtual bool is_utf8() const = 0; + bool is_eof() const { return eof; } + Stream() {} virtual ~Stream() {} }; struct StreamFile : public Stream { + protected: + virtual uint32_t _read_buffer(char32_t *p_buffer, uint32_t p_num_chars) override; + + public: Ref<FileAccess> f; - virtual char32_t get_char() override; virtual bool is_utf8() const override; - virtual bool is_eof() const override; StreamFile() {} }; struct StreamString : public Stream { String s; + + private: int pos = 0; - virtual char32_t get_char() override; - virtual bool is_utf8() const override; - virtual bool is_eof() const override; + protected: + virtual uint32_t _read_buffer(char32_t *p_buffer, uint32_t p_num_chars) override; + public: + virtual bool is_utf8() const override; StreamString() {} }; diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 33afe38446..4a6b85f229 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -115,7 +115,7 @@ <param index="3" name="end" type="float" /> <param index="4" name="t" type="float" /> <description> - Returns the derivative at the given [param t] on a one-dimensional [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bezier curve[/url] defined by the given [param control_1], [param control_2], and [param end] points. + Returns the derivative at the given [param t] on a one-dimensional [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bézier curve[/url] defined by the given [param control_1], [param control_2], and [param end] points. </description> </method> <method name="bezier_interpolate"> @@ -126,7 +126,7 @@ <param index="3" name="end" type="float" /> <param index="4" name="t" type="float" /> <description> - Returns the point at the given [param t] on a one-dimensional [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bezier curve[/url] defined by the given [param control_1], [param control_2], and [param end] points. + Returns the point at the given [param t] on a one-dimensional [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bézier curve[/url] defined by the given [param control_1], [param control_2], and [param end] points. </description> </method> <method name="bytes_to_var"> @@ -2877,40 +2877,38 @@ </constant> <constant name="PROPERTY_USAGE_STORE_IF_NULL" value="8192" enum="PropertyUsageFlags"> </constant> - <constant name="PROPERTY_USAGE_ANIMATE_AS_TRIGGER" value="16384" enum="PropertyUsageFlags"> + <constant name="PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED" value="16384" enum="PropertyUsageFlags"> </constant> - <constant name="PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED" value="32768" enum="PropertyUsageFlags"> + <constant name="PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE" value="32768" enum="PropertyUsageFlags"> </constant> - <constant name="PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE" value="65536" enum="PropertyUsageFlags"> + <constant name="PROPERTY_USAGE_CLASS_IS_ENUM" value="65536" enum="PropertyUsageFlags"> </constant> - <constant name="PROPERTY_USAGE_CLASS_IS_ENUM" value="131072" enum="PropertyUsageFlags"> + <constant name="PROPERTY_USAGE_NIL_IS_VARIANT" value="131072" enum="PropertyUsageFlags"> </constant> - <constant name="PROPERTY_USAGE_NIL_IS_VARIANT" value="262144" enum="PropertyUsageFlags"> + <constant name="PROPERTY_USAGE_INTERNAL" value="262144" enum="PropertyUsageFlags"> </constant> - <constant name="PROPERTY_USAGE_INTERNAL" value="524288" enum="PropertyUsageFlags"> - </constant> - <constant name="PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE" value="1048576" enum="PropertyUsageFlags"> + <constant name="PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE" value="524288" enum="PropertyUsageFlags"> If the property is a [Resource], a new copy of it is always created when calling [method Node.duplicate] or [method Resource.duplicate]. </constant> - <constant name="PROPERTY_USAGE_HIGH_END_GFX" value="2097152" enum="PropertyUsageFlags"> + <constant name="PROPERTY_USAGE_HIGH_END_GFX" value="1048576" enum="PropertyUsageFlags"> The property is only shown in the editor if modern renderers are supported (GLES3 is excluded). </constant> - <constant name="PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT" value="4194304" enum="PropertyUsageFlags"> + <constant name="PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT" value="2097152" enum="PropertyUsageFlags"> </constant> - <constant name="PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT" value="8388608" enum="PropertyUsageFlags"> + <constant name="PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT" value="4194304" enum="PropertyUsageFlags"> </constant> - <constant name="PROPERTY_USAGE_KEYING_INCREMENTS" value="16777216" enum="PropertyUsageFlags"> + <constant name="PROPERTY_USAGE_KEYING_INCREMENTS" value="8388608" enum="PropertyUsageFlags"> </constant> - <constant name="PROPERTY_USAGE_DEFERRED_SET_RESOURCE" value="33554432" enum="PropertyUsageFlags"> + <constant name="PROPERTY_USAGE_DEFERRED_SET_RESOURCE" value="16777216" enum="PropertyUsageFlags"> </constant> - <constant name="PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT" value="67108864" enum="PropertyUsageFlags"> + <constant name="PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT" value="33554432" enum="PropertyUsageFlags"> </constant> - <constant name="PROPERTY_USAGE_EDITOR_BASIC_SETTING" value="134217728" enum="PropertyUsageFlags"> + <constant name="PROPERTY_USAGE_EDITOR_BASIC_SETTING" value="67108864" enum="PropertyUsageFlags"> </constant> - <constant name="PROPERTY_USAGE_READ_ONLY" value="268435456" enum="PropertyUsageFlags"> + <constant name="PROPERTY_USAGE_READ_ONLY" value="134217728" enum="PropertyUsageFlags"> The property is read-only in the [EditorInspector]. </constant> - <constant name="PROPERTY_USAGE_ARRAY" value="536870912" enum="PropertyUsageFlags"> + <constant name="PROPERTY_USAGE_ARRAY" value="268435456" enum="PropertyUsageFlags"> The property is an array. </constant> <constant name="PROPERTY_USAGE_DEFAULT" value="6" enum="PropertyUsageFlags"> diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml index 58681090b4..d9a1f896f1 100644 --- a/doc/classes/Animation.xml +++ b/doc/classes/Animation.xml @@ -596,15 +596,12 @@ [b]Note:[/b] The result value is always normalized and may not match the key value. </constant> <constant name="UPDATE_CONTINUOUS" value="0" enum="UpdateMode"> - Update between keyframes. + Update between keyframes and hold the value. </constant> <constant name="UPDATE_DISCRETE" value="1" enum="UpdateMode"> - Update at the keyframes and hold the value. - </constant> - <constant name="UPDATE_TRIGGER" value="2" enum="UpdateMode"> Update at the keyframes. </constant> - <constant name="UPDATE_CAPTURE" value="3" enum="UpdateMode"> + <constant name="UPDATE_CAPTURE" value="2" enum="UpdateMode"> Same as linear interpolation, but also interpolates from the current value (i.e. dynamically at runtime) if the first key isn't at 0 seconds. </constant> <constant name="LOOP_NONE" value="0" enum="LoopMode"> diff --git a/doc/classes/Color.xml b/doc/classes/Color.xml index 90fbdcc622..4d78433915 100644 --- a/doc/classes/Color.xml +++ b/doc/classes/Color.xml @@ -1,12 +1,11 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="Color" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Color in RGBA format using floats on the range of 0 to 1. + Color built-in type, in RGBA format. </brief_description> <description> - A color represented by red, green, blue, and alpha (RGBA) components. The alpha component is often used for opacity. Values are in floating-point and usually range from 0 to 1. Some properties (such as CanvasItem.modulate) may accept values greater than 1 (overbright or HDR colors). - You can also create a color from standardized color names by using the string constructor or directly using the color constants defined here. The standardized color set is based on the [url=https://en.wikipedia.org/wiki/X11_color_names]X11 color names[/url]. - If you want to supply values in a range of 0 to 255, you should use [method @GDScript.Color8]. + A color represented in RGBA format by red ([member r]), green ([member g]), blue ([member b]), and alpha ([member a]) components. Each component is a 16-bit floating-point value, usually ranging from 0 to 1. Some properties (such as [member CanvasItem.modulate]) may support values greater than 1, for overbright or High Dynamic Range colors. If you want to supply values in a range of 0 to 255, you should use [method @GDScript.Color8]. + Colors can also be created by name from a set of standardized colors, through the [String] constructor, [method from_string], or by directly fetching the color constants documented here. The standardized color set is based on the [url=https://en.wikipedia.org/wiki/X11_color_names]X11 color names[/url], with the addition of [constant TRANSPARENT]. [b]Note:[/b] In a boolean context, a Color will evaluate to [code]false[/code] if it's equal to [code]Color(0, 0, 0, 1)[/code] (opaque black). Otherwise, a Color will always evaluate to [code]true[/code]. [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/color_constants.png]Color constants cheatsheet[/url] </description> @@ -19,7 +18,8 @@ <constructor name="Color"> <return type="Color" /> <description> - Constructs a default-initialized [Color] with all components set to [code]0[/code]. + Constructs a default [Color] from opaque black. This is the same as [constant BLACK]. + [b]Note:[/b] in C#, constructs an empty color with all of its components set to [code]0.0[/code] (transparent black). </description> </constructor> <constructor name="Color"> @@ -27,10 +27,10 @@ <param index="0" name="from" type="Color" /> <param index="1" name="alpha" type="float" /> <description> - Constructs a [Color] from an existing color, but with a custom alpha value. + Constructs a [Color] from the existing color, with [member a] set to the given [param alpha] value. [codeblocks] [gdscript] - var red = Color(Color.red, 0.2) # 20% opaque red. + var red = Color(Color.RED, 0.2) # 20% opaque red. [/gdscript] [csharp] var red = new Color(Colors.Red, 0.2f); // 20% opaque red. @@ -49,7 +49,7 @@ <return type="Color" /> <param index="0" name="code" type="String" /> <description> - Constructs a [Color] either from an HTML color code or from a standardized color name. Supported color names are the same as the constants. + Constructs a [Color] either from an HTML color code or from a standardized color name. The supported color names are the same as the constants. </description> </constructor> <constructor name="Color"> @@ -57,7 +57,7 @@ <param index="0" name="code" type="String" /> <param index="1" name="alpha" type="float" /> <description> - Constructs a [Color] either from an HTML color code or from a standardized color name, with [param alpha] on the range of 0 to 1. Supported color names are the same as the constants. + Constructs a [Color] either from an HTML color code or from a standardized color name, with [param alpha] on the range of 0.0 to 1.0. The supported color names are the same as the constants. </description> </constructor> <constructor name="Color"> @@ -66,7 +66,7 @@ <param index="1" name="g" type="float" /> <param index="2" name="b" type="float" /> <description> - Constructs a [Color] from RGB values, typically between 0 and 1. Alpha will be 1. + Constructs a [Color] from RGB values, typically between 0.0 and 1.0. [member a] is set to 1.0. [codeblocks] [gdscript] var color = Color(0.2, 1.0, 0.7) # Similar to `Color8(51, 255, 178, 255)` @@ -84,7 +84,7 @@ <param index="2" name="b" type="float" /> <param index="3" name="a" type="float" /> <description> - Constructs a [Color] from RGBA values, typically between 0 and 1. + Constructs a [Color] from RGBA values, typically between 0.0 and 1.0. [codeblocks] [gdscript] var color = Color(0.2, 1.0, 0.7, 0.8) # Similar to `Color8(51, 255, 178, 204)` @@ -101,7 +101,7 @@ <return type="Color" /> <param index="0" name="over" type="Color" /> <description> - Returns a new color resulting from blending this color over another. If the color is opaque, the result is also opaque. The second color may have a range of alpha values. + Returns a new color resulting from overlaying this color over the given color. In a painting program, you can imagine it as the [param over] color painted over this colour (including alpha). [codeblocks] [gdscript] var bg = Color(0.0, 1.0, 0.0, 0.5) # Green with alpha of 50% @@ -128,7 +128,7 @@ <return type="Color" /> <param index="0" name="amount" type="float" /> <description> - Returns a new color resulting from making this color darker by the specified percentage (ratio from 0 to 1). + Returns a new color resulting from making this color darker by the specified [param amount] (ratio from 0.0 to 1.0). See also [method lightened]. [codeblocks] [gdscript] var green = Color(0.0, 1.0, 0.0) @@ -148,7 +148,7 @@ <param index="2" name="v" type="float" /> <param index="3" name="alpha" type="float" default="1.0" /> <description> - Constructs a color from an [url=https://en.wikipedia.org/wiki/HSL_and_HSV]HSV profile[/url]. [param h] (hue), [param s] (saturation), and [param v] (value) are typically between 0 and 1. + Constructs a color from an [url=https://en.wikipedia.org/wiki/HSL_and_HSV]HSV profile[/url]. The hue ([param h]), saturation ([param s]), and value ([param v]) are typically between 0.0 and 1.0. [codeblocks] [gdscript] var color = Color.from_hsv(0.58, 0.5, 0.79, 0.8) @@ -166,7 +166,7 @@ <param index="2" name="l" type="float" /> <param index="3" name="alpha" type="float" default="1.0" /> <description> - Constructs a color from an [url=https://bottosson.github.io/posts/colorpicker/]OK HSL profile[/url]. [param h] (hue), [param s] (saturation), and [param l] (lightness) are typically between 0 and 1. + Constructs a color from an [url=https://bottosson.github.io/posts/colorpicker/]OK HSL profile[/url]. The hue ([param h]), saturation ([param s]), and lightness ([param l]) are typically between 0.0 and 1.0. [codeblocks] [gdscript] var color = Color.from_ok_hsl(0.58, 0.5, 0.79, 0.8) @@ -189,14 +189,13 @@ <param index="0" name="str" type="String" /> <param index="1" name="default" type="Color" /> <description> - Creates a [Color] from string, which can be either a HTML color code or a named color. Fallbacks to [param default] if the string does not denote any valid color. + Creates a [Color] from the given string, which can be either an HTML color code or a named color (case-insensitive). Returns [param default] if the color cannot be inferred from the string. </description> </method> <method name="get_luminance" qualifiers="const"> <return type="float" /> <description> - Returns the luminance of the color in the [code][0.0, 1.0][/code] range. - This is useful when determining light or dark color. Colors with a luminance smaller than 0.5 can be generally considered dark. + Returns the light intensity of the color, as a value between 0.0 and 1.0 (inclusive). This is useful when determining light or dark color. Colors with a luminance smaller than 0.5 can be generally considered dark. [b]Note:[/b] [method get_luminance] relies on the color being in the linear color space to return an accurate relative luminance value. If the color is in the sRGB color space, use [method srgb_to_linear] to convert it to the linear color space first. </description> </method> @@ -204,9 +203,12 @@ <return type="Color" /> <param index="0" name="hex" type="int" /> <description> - Returns the [Color] associated with the provided integer number, with 8 bits per channel in ARGB order. The integer should be 32-bit. Best used with hexadecimal notation. + Returns the [Color] associated with the provided [param hex] integer in 32-bit ARGB format (8 bits per channel, alpha channel first). + In GDScript and C#, the [int] is best visualized with hexadecimal notation ([code]"0x"[/code] prefix). [codeblock] - modulate = Color.hex(0xffff0000) # red + var red = Color.hex(0xffff0000) + var dark_cyan = Color.hex(0xff008b8b) + var my_color = Color.hex(0xa4bbefd2) [/codeblock] </description> </method> @@ -214,25 +216,27 @@ <return type="Color" /> <param index="0" name="hex" type="int" /> <description> - Same as [method hex], but takes 64-bit integer and the color uses 16 bits per channel. + Returns the [Color] associated with the provided [param hex] integer in 64-bit ARGB format (16 bits per channel, alpha channel first). + In GDScript and C#, the [int] is best visualized with hexadecimal notation ([code]"0x"[/code] prefix). </description> </method> <method name="html" qualifiers="static"> <return type="Color" /> <param index="0" name="rgba" type="String" /> <description> - Returns a new color from [param rgba], an HTML hexadecimal color string. [param rgba] is not case sensitive, and may be prefixed with a '#' character. - [param rgba] must be a valid three-digit or six-digit hexadecimal color string, and may contain an alpha channel value. If [param rgba] does not contain an alpha channel value, an alpha channel value of 1.0 is applied. - If [param rgba] is invalid a Color(0.0, 0.0, 0.0, 1.0) is returned. - [b]Note:[/b] This method is not implemented in C#, but the same functionality is provided in the class constructor. + Returns a new color from [param rgba], an HTML hexadecimal color string. [param rgba] is not case-sensitive, and may be prefixed by a hash sign ([code]#[/code]). + [param rgba] must be a valid three-digit or six-digit hexadecimal color string, and may contain an alpha channel value. If [param rgba] does not contain an alpha channel value, an alpha channel value of 1.0 is applied. If [param rgba] is invalid, returns an empty color. + [b]Note:[/b] In C#, this method is not implemented. The same functionality is provided by the Color constructor. [codeblocks] [gdscript] - var green = Color.html("#00FF00FF") # set green to Color(0.0, 1.0, 0.0, 1.0) - var blue = Color.html("#0000FF") # set blue to Color(0.0, 0.0, 1.0, 1.0) + var blue = Color.html("#0000ff") # blue is Color(0.0, 0.0, 1.0, 1.0) + var green = Color.html("#0F0") # green is Color(0.0, 1.0, 0.0, 1.0) + var col = Color.html("663399cc") # col is Color(0.4, 0.2, 0.6, 0.8) [/gdscript] [csharp] - var green = new Color("#00FF00FF"); // set green to Color(0.0, 1.0, 0.0, 1.0) - var blue = new Color("#0000FF"); // set blue to Color(0.0, 0.0, 1.0, 1.0) + var blue = new Color("#0000ff"); // blue is Color(0.0, 0.0, 1.0, 1.0) + var green = new Color("#0F0"); // green is Color(0.0, 1.0, 0.0, 1.0) + var col = new Color("663399cc"); // col is Color(0.4, 0.2, 0.6, 0.8) [/csharp] [/codeblocks] </description> @@ -241,24 +245,26 @@ <return type="bool" /> <param index="0" name="color" type="String" /> <description> - Returns [code]true[/code] if [param color] is a valid HTML hexadecimal color string. [param color] is not case sensitive, and may be prefixed with a '#' character. - For a string to be valid it must be three-digit or six-digit hexadecimal, and may contain an alpha channel value. + Returns [code]true[/code] if [param color] is a valid HTML hexadecimal color string. The string must be a hexadecimal value (case-insensitive) of either 3, 4, 6 or 8 digits, and may be prefixed by a hash sign ([code]#[/code]). This method is identical to [method String.is_valid_html_color]. [codeblocks] [gdscript] - var result = Color.html_is_valid("#55aaFF") # result is true - result = Color.html_is_valid("#55AAFF20") # result is true - result = Color.html_is_valid("55AAFF") # result is true - result = Color.html_is_valid("#F2C") # result is true - result = Color.html_is_valid("#AABBC) # result is false - result = Color.html_is_valid("#55aaFF5") # result is false + Color.html_is_valid("#55aaFF") # Returns true + Color.html_is_valid("#55AAFF20") # Returns true + Color.html_is_valid("55AAFF") # Returns true + Color.html_is_valid("#F2C") # Returns true + + Color.html_is_valid("#AABBC) # Returns false + Color.html_is_valid("#55aaFF5") # Returns false [/gdscript] [csharp] - var result = Color.HtmlIsValid("#55AAFF"); // result is true - result = Color.HtmlIsValid("#55AAFF20"); // result is true - result = Color.HtmlIsValid("55AAFF); // result is true - result = Color.HtmlIsValid("#F2C"); // result is true - result = Color.HtmlIsValid("#AABBC"); // result is false - result = Color.HtmlIsValid("#55aaFF5"); // result is false + // This method is not available in C#. Use `StringExtensions.IsValidHtmlColor()`, instead. + "#55AAFF".IsValidHtmlColor(); // Returns true + "#55AAFF20".IsValidHtmlColor(); // Returns true + "55AAFF".IsValidHtmlColor(); // Returns true + "#F2C".IsValidHtmlColor(); // Returns true + + "#AABBC".IsValidHtmlColor(); // Returns false + "#55aaFF5".IsValidHtmlColor(); // Returns false [/csharp] [/codeblocks] </description> @@ -266,13 +272,15 @@ <method name="inverted" qualifiers="const"> <return type="Color" /> <description> - Returns the inverted color [code](1 - r, 1 - g, 1 - b, a)[/code]. + Returns the color with its [member r], [member g], and [member b] components inverted ([code](1 - r, 1 - g, 1 - b, a)[/code]). [codeblocks] [gdscript] + var black = Color.WHITE.inverted() var color = Color(0.3, 0.4, 0.9) var inverted_color = color.inverted() # Equivalent to `Color(0.7, 0.6, 0.1)` [/gdscript] [csharp] + var black = Colors.White.Inverted(); var color = new Color(0.3f, 0.4f, 0.9f); Color invertedColor = color.Inverted(); // Equivalent to `new Color(0.7f, 0.6f, 0.1f)` [/csharp] @@ -291,17 +299,23 @@ <param index="0" name="to" type="Color" /> <param index="1" name="weight" type="float" /> <description> - Returns the linear interpolation with another color. The interpolation factor [param weight] is between 0 and 1. + Returns the linear interpolation between this color's components and [param to]'s components. The interpolation factor [param weight] should be between 0.0 and 1.0 (inclusive). See also [method @GlobalScope.lerp]. [codeblocks] [gdscript] - var c1 = Color(1.0, 0.0, 0.0) - var c2 = Color(0.0, 1.0, 0.0) - var lerp_color = c1.lerp(c2, 0.5) # Equivalent to `Color(0.5, 0.5, 0.0)` + var red = Color(1.0, 0.0, 0.0) + var aqua = Color(0.0, 1.0, 0.8) + + red.lerp(aqua, 0.2) # Returns Color(0.8, 0.2, 0.16) + red.lerp(aqua, 0.5) # Returns Color(0.5, 0.5, 0.4) + red.lerp(aqua, 1.0) # Returns Color(0.0, 1.0, 0.8) [/gdscript] [csharp] - var c1 = new Color(1.0f, 0.0f, 0.0f); - var c2 = new Color(0.0f, 1.0f, 0.0f); - Color lerpColor = c1.Lerp(c2, 0.5f); // Equivalent to `new Color(0.5f, 0.5f, 0.0f)` + var red = new Color(1.0f, 0.0f, 0.0f); + var aqua = new Color(0.0f, 1.0f, 0.8f); + + red.Lerp(aqua, 0.2f); // Returns Color(0.8f, 0.2f, 0.16f) + red.Lerp(aqua, 0.5f); // Returns Color(0.5f, 0.5f, 0.4f) + red.Lerp(aqua, 1.0f); // Returns Color(0.0f, 1.0f, 0.8f) [/csharp] [/codeblocks] </description> @@ -310,15 +324,15 @@ <return type="Color" /> <param index="0" name="amount" type="float" /> <description> - Returns a new color resulting from making this color lighter by the specified percentage (ratio from 0 to 1). + Returns a new color resulting from making this color lighter by the specified [param amount], which should be a ratio from 0.0 to 1.0. See also [method darkened]. [codeblocks] [gdscript] var green = Color(0.0, 1.0, 0.0) - var lightgreen = green.lightened(0.2) # 20% lighter than regular green + var light_green = green.lightened(0.2) # 20% lighter than regular green [/gdscript] [csharp] var green = new Color(0.0f, 1.0f, 0.0f); - Color lightgreen = green.Lightened(0.2f); // 20% lighter than regular green + Color lightGreen = green.Lightened(0.2f); // 20% lighter than regular green [/csharp] [/codeblocks] </description> @@ -326,19 +340,19 @@ <method name="linear_to_srgb" qualifiers="const"> <return type="Color" /> <description> - Returns the color converted to the [url=https://en.wikipedia.org/wiki/SRGB]sRGB[/url] color space. This assumes the original color is in the linear color space. See also [method srgb_to_linear] which performs the opposite operation. + Returns the color converted to the [url=https://en.wikipedia.org/wiki/SRGB]sRGB[/url] color space. This method assumes the original color is in the linear color space. See also [method srgb_to_linear] which performs the opposite operation. </description> </method> <method name="srgb_to_linear" qualifiers="const"> <return type="Color" /> <description> - Returns the color converted to the linear color space. This assumes the original color is in the sRGB color space. See also [method linear_to_srgb] which performs the opposite operation. + Returns the color converted to the linear color space. This method assumes the original color already is in the sRGB color space. See also [method linear_to_srgb] which performs the opposite operation. </description> </method> <method name="to_abgr32" qualifiers="const"> <return type="int" /> <description> - Returns the color converted to a 32-bit integer in ABGR format (each byte represents a color channel). ABGR is the reversed version of the default format. + Returns the color converted to a 32-bit integer in ABGR format (each component is 8 bits). ABGR is the reversed version of the default RGBA format. [codeblocks] [gdscript] var color = Color(1, 0.5, 0.2) @@ -354,7 +368,7 @@ <method name="to_abgr64" qualifiers="const"> <return type="int" /> <description> - Returns the color converted to a 64-bit integer in ABGR format (each word represents a color channel). ABGR is the reversed version of the default format. + Returns the color converted to a 64-bit integer in ABGR format (each component is 16 bits). ABGR is the reversed version of the default RGBA format. [codeblocks] [gdscript] var color = Color(1, 0.5, 0.2) @@ -370,7 +384,7 @@ <method name="to_argb32" qualifiers="const"> <return type="int" /> <description> - Returns the color converted to a 32-bit integer in ARGB format (each byte represents a color channel). ARGB is more compatible with DirectX. + Returns the color converted to a 32-bit integer in ARGB format (each component is 8 bits). ARGB is more compatible with DirectX. [codeblocks] [gdscript] var color = Color(1, 0.5, 0.2) @@ -386,7 +400,7 @@ <method name="to_argb64" qualifiers="const"> <return type="int" /> <description> - Returns the color converted to a 64-bit integer in ARGB format (each word represents a color channel). ARGB is more compatible with DirectX. + Returns the color converted to a 64-bit integer in ARGB format (each component is 16 bits). ARGB is more compatible with DirectX. [codeblocks] [gdscript] var color = Color(1, 0.5, 0.2) @@ -403,18 +417,18 @@ <return type="String" /> <param index="0" name="with_alpha" type="bool" default="true" /> <description> - Returns the color converted to an HTML hexadecimal color string in RGBA format (ex: [code]ff34f822[/code]). - Setting [param with_alpha] to [code]false[/code] excludes alpha from the hexadecimal string (and uses RGB instead of RGBA format). + Returns the color converted to an HTML hexadecimal color [String] in RGBA format, without the hash ([code]#[/code]) prefix. + Setting [param with_alpha] to [code]false[/code], excludes alpha from the hexadecimal string, using RGB format instead of RGBA format. [codeblocks] [gdscript] - var color = Color(1, 1, 1, 0.5) - var with_alpha = color.to_html() # Returns "ffffff7f" - var without_alpha = color.to_html(false) # Returns "ffffff" + var white = Color(1, 1, 1, 0.5) + var with_alpha = white.to_html() # Returns "ffffff7f" + var without_alpha = white.to_html(false) # Returns "ffffff" [/gdscript] [csharp] - var color = new Color(1, 1, 1, 0.5f); - String withAlpha = color.ToHtml(); // Returns "ffffff7f" - String withoutAlpha = color.ToHtml(false); // Returns "ffffff" + var white = new Color(1, 1, 1, 0.5f); + string withAlpha = white.ToHtml(); // Returns "ffffff7f" + string withoutAlpha = white.ToHtml(false); // Returns "ffffff" [/csharp] [/codeblocks] </description> @@ -422,7 +436,7 @@ <method name="to_rgba32" qualifiers="const"> <return type="int" /> <description> - Returns the color converted to a 32-bit integer in RGBA format (each byte represents a color channel). RGBA is Godot's default format. + Returns the color converted to a 32-bit integer in RGBA format (each component is 8 bits). RGBA is Godot's default format. [codeblocks] [gdscript] var color = Color(1, 0.5, 0.2) @@ -438,7 +452,7 @@ <method name="to_rgba64" qualifiers="const"> <return type="int" /> <description> - Returns the color converted to a 64-bit integer in RGBA format (each word represents a color channel). RGBA is Godot's default format. + Returns the color converted to a 64-bit integer in RGBA format (each component is 16 bits). RGBA is Godot's default format. [codeblocks] [gdscript] var color = Color(1, 0.5, 0.2) @@ -457,19 +471,19 @@ The color's alpha component, typically on the range of 0 to 1. A value of 0 means that the color is fully transparent. A value of 1 means that the color is fully opaque. </member> <member name="a8" type="int" setter="" getter="" default="255"> - Wrapper for [member a] that uses the range 0 to 255 instead of 0 to 1. + Wrapper for [member a] that uses the range 0 to 255, instead of 0 to 1. </member> <member name="b" type="float" setter="" getter="" default="0.0"> The color's blue component, typically on the range of 0 to 1. </member> <member name="b8" type="int" setter="" getter="" default="0"> - Wrapper for [member b] that uses the range 0 to 255 instead of 0 to 1. + Wrapper for [member b] that uses the range 0 to 255, instead of 0 to 1. </member> <member name="g" type="float" setter="" getter="" default="0.0"> The color's green component, typically on the range of 0 to 1. </member> <member name="g8" type="int" setter="" getter="" default="0"> - Wrapper for [member g] that uses the range 0 to 255 instead of 0 to 1. + Wrapper for [member g] that uses the range 0 to 255, instead of 0 to 1. </member> <member name="h" type="float" setter="" getter="" default="0.0"> The HSV hue of this color, on the range 0 to 1. @@ -478,7 +492,7 @@ The color's red component, typically on the range of 0 to 1. </member> <member name="r8" type="int" setter="" getter="" default="0"> - Wrapper for [member r] that uses the range 0 to 255 instead of 0 to 1. + Wrapper for [member r] that uses the range 0 to 255, instead of 0 to 1. </member> <member name="s" type="float" setter="" getter="" default="0.0"> The HSV saturation of this color, on the range 0 to 1. @@ -510,7 +524,7 @@ Bisque color. </constant> <constant name="BLACK" value="Color(0, 0, 0, 1)"> - Black color. + Black color. In GDScript, this is the default value of any color. </constant> <constant name="BLANCHED_ALMOND" value="Color(1, 0.921569, 0.803922, 1)"> Blanched almond color. @@ -932,7 +946,7 @@ <return type="bool" /> <param index="0" name="right" type="Color" /> <description> - Returns [code]true[/code] if the colors are not equal. + Returns [code]true[/code] if the colors are not exactly equal. [b]Note:[/b] Due to floating-point precision errors, consider using [method is_equal_approx] instead, which is more reliable. </description> </operator> @@ -1004,7 +1018,7 @@ <return type="float" /> <param index="0" name="index" type="int" /> <description> - Access color components using their index. [code]c[0][/code] is equivalent to [code]c.r[/code], [code]c[1][/code] is equivalent to [code]c.g[/code], [code]c[2][/code] is equivalent to [code]c.b[/code], and [code]c[3][/code] is equivalent to [code]c.a[/code]. + Access color components using their index. [code][0][/code] is equivalent to [member r], [code][1][/code] is equivalent to [member g], [code][2][/code] is equivalent to [member b], and [code][3][/code] is equivalent to [member a]. </description> </operator> <operator name="operator unary+"> @@ -1016,7 +1030,7 @@ <operator name="operator unary-"> <return type="Color" /> <description> - Inverts the given color. This is equivalent to [code]Color.WHITE - c[/code] or [code]Color(1 - c.r, 1 - c.g, 1 - c.b, 1 - c.a)[/code]. + Inverts the given color. This is equivalent to [code]Color.WHITE - c[/code] or [code]Color(1 - c.r, 1 - c.g, 1 - c.b, 1 - c.a)[/code]. Unlike with [method inverted], the [member a] component is inverted, too. </description> </operator> </operators> diff --git a/doc/classes/Curve2D.xml b/doc/classes/Curve2D.xml index ccdf085319..b5e75dff68 100644 --- a/doc/classes/Curve2D.xml +++ b/doc/classes/Curve2D.xml @@ -51,7 +51,7 @@ <return type="Vector2" /> <param index="0" name="to_point" type="Vector2" /> <description> - Returns the closest baked point (in curve's local space) to [param to_point]. + Returns the closest point on baked segments (in curve's local space) to [param to_point]. [param to_point] must be in this curve's local space. </description> </method> @@ -94,7 +94,7 @@ </method> <method name="sample_baked" qualifiers="const"> <return type="Vector2" /> - <param index="0" name="offset" type="float" /> + <param index="0" name="offset" type="float" default="0.0" /> <param index="1" name="cubic" type="bool" default="false" /> <description> Returns a point within the curve at position [param offset], where [param offset] is measured as a pixel distance along the curve. @@ -104,13 +104,10 @@ </method> <method name="sample_baked_with_rotation" qualifiers="const"> <return type="Transform2D" /> - <param index="0" name="offset" type="float" /> + <param index="0" name="offset" type="float" default="0.0" /> <param index="1" name="cubic" type="bool" default="false" /> - <param index="2" name="loop" type="bool" default="true" /> - <param index="3" name="lookahead" type="float" default="4.0" /> <description> Similar to [method sample_baked], but returns [Transform2D] that includes a rotation along the curve. Returns empty transform if length of the curve is [code]0[/code]. - Use [param loop] to smooth the tangent at the end of the curve. [param lookahead] defines the distance to a nearby point for calculating the tangent vector. [codeblock] var transform = curve.sample_baked_with_rotation(offset) position = transform.get_origin() @@ -160,6 +157,13 @@ [param tolerance_degrees] controls how many degrees the midpoint of a segment may deviate from the real curve, before the segment has to be subdivided. </description> </method> + <method name="tessellate_even_length" qualifiers="const"> + <return type="PackedVector2Array" /> + <param index="0" name="max_stages" type="int" default="5" /> + <param index="1" name="tolerance_length" type="float" default="20.0" /> + <description> + </description> + </method> </methods> <members> <member name="bake_interval" type="float" setter="set_bake_interval" getter="get_bake_interval" default="5.0"> diff --git a/doc/classes/Curve3D.xml b/doc/classes/Curve3D.xml index 851996131f..cfe2036499 100644 --- a/doc/classes/Curve3D.xml +++ b/doc/classes/Curve3D.xml @@ -114,7 +114,7 @@ </method> <method name="sample_baked" qualifiers="const"> <return type="Vector3" /> - <param index="0" name="offset" type="float" /> + <param index="0" name="offset" type="float" default="0.0" /> <param index="1" name="cubic" type="bool" default="false" /> <description> Returns a point within the curve at position [param offset], where [param offset] is measured as a distance in 3D units along the curve. @@ -134,7 +134,7 @@ </method> <method name="sample_baked_with_rotation" qualifiers="const"> <return type="Transform3D" /> - <param index="0" name="offset" type="float" /> + <param index="0" name="offset" type="float" default="0.0" /> <param index="1" name="cubic" type="bool" default="false" /> <param index="2" name="apply_tilt" type="bool" default="false" /> <description> diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml index 9842288d35..92225b816f 100644 --- a/doc/classes/Dictionary.xml +++ b/doc/classes/Dictionary.xml @@ -4,9 +4,8 @@ Dictionary type. </brief_description> <description> - Dictionary type. Associative container, which contains values referenced by unique keys. Dictionaries are composed of pairs of keys (which must be unique) and values. Dictionaries will preserve the insertion order when adding elements. In other programming languages, this data structure is sometimes referred to as a hash map or associative array. + Dictionary type. Associative container, which contains values referenced by unique keys. Dictionaries are composed of pairs of keys (which must be unique) and values. Dictionaries will preserve the insertion order when adding new entries. In other programming languages, this data structure is sometimes referred to as a hash map or associative array. You can define a dictionary by placing a comma-separated list of [code]key: value[/code] pairs in curly braces [code]{}[/code]. - Erasing elements while iterating over them [b]is not supported[/b] and will result in undefined behavior. [b]Note:[/b] Dictionaries are always passed by reference. To get a copy of a dictionary which can be modified independently of the original dictionary, use [method duplicate]. Creating a dictionary: [codeblocks] @@ -40,10 +39,10 @@ }; [/csharp] [/codeblocks] - You can access a dictionary's values by referencing the appropriate key. In the above example, [code]points_dict["White"][/code] will return [code]50[/code]. You can also write [code]points_dict.White[/code], which is equivalent. However, you'll have to use the bracket syntax if the key you're accessing the dictionary with isn't a fixed string (such as a number or variable). + You can access a dictionary's value by referencing its corresponding key. In the above example, [code]points_dict["White"][/code] will return [code]50[/code]. You can also write [code]points_dict.White[/code], which is equivalent. However, you'll have to use the bracket syntax if the key you're accessing the dictionary with isn't a fixed string (such as a number or variable). [codeblocks] [gdscript] - export(String, "White", "Yellow", "Orange") var my_color + @export(String, "White", "Yellow", "Orange") var my_color var points_dict = {"White": 50, "Yellow": 75, "Orange": 100} func _ready(): # We can't use dot syntax here as `my_color` is a variable. @@ -69,7 +68,9 @@ Dictionaries can contain more complex data: [codeblocks] [gdscript] - my_dict = {"First Array": [1, 2, 3, 4]} # Assigns an Array to a String key. + var my_dict = { + "First Array": [1, 2, 3, 4] # Assigns an Array to a String key. + } [/gdscript] [csharp] var myDict = new Godot.Collections.Dictionary @@ -91,7 +92,7 @@ {"Yellow", 75}, {"Orange", 100} }; - pointsDict["blue"] = 150; // Add "Blue" as a key and assign 150 as its value. + pointsDict["Blue"] = 150; // Add "Blue" as a key and assign 150 as its value. [/csharp] [/codeblocks] Finally, dictionaries can contain different types of keys and values in the same dictionary: @@ -118,63 +119,23 @@ }; [/csharp] [/codeblocks] - [b]Note:[/b] Unlike [Array]s, you can't compare dictionaries directly: + The keys of a dictionary can be iterated with the [code]for[/code] keyword: [codeblocks] [gdscript] - var array1 = [1, 2, 3] - var array2 = [1, 2, 3] - - func compare_arrays(): - print(array1 == array2) # Will print true. - - var dict1 = {"a": 1, "b": 2, "c": 3} - var dict2 = {"a": 1, "b": 2, "c": 3} - - func compare_dictionaries(): - print(dict1 == dict2) # Will NOT print true. + var groceries = {"Orange": 20, "Apple": 2, "Banana": 4} + for fruit in groceries: + var amount = groceries[fruit] [/gdscript] [csharp] - // You have to use GD.Hash(). - - public Godot.Collections.Array array1 = new Godot.Collections.Array{1, 2, 3}; - public Godot.Collections.Array array2 = new Godot.Collections.Array{1, 2, 3}; - - public void CompareArrays() + var groceries = new Godot.Collections.Dictionary{{"Orange", 20}, {"Apple", 2}, {"Banana", 4}}; + foreach (var (fruit, amount) in groceries) { - GD.Print(array1 == array2); // Will print FALSE!! - GD.Print(GD.Hash(array1) == GD.Hash(array2)); // Will print true. - } - - public Godot.Collections.Dictionary dict1 = new Godot.Collections.Dictionary{{"a", 1}, {"b", 2}, {"c", 3}}; - public Godot.Collections.Dictionary dict2 = new Godot.Collections.Dictionary{{"a", 1}, {"b", 2}, {"c", 3}}; - - public void CompareDictionaries() - { - GD.Print(dict1 == dict2); // Will NOT print true. + // `fruit` is the key, `amount` is the value. } [/csharp] [/codeblocks] - You need to first calculate the dictionary's hash with [method hash] before you can compare them: - [codeblocks] - [gdscript] - var dict1 = {"a": 1, "b": 2, "c": 3} - var dict2 = {"a": 1, "b": 2, "c": 3} - - func compare_dictionaries(): - print(dict1.hash() == dict2.hash()) # Will print true. - [/gdscript] - [csharp] - // You have to use GD.Hash(). - public Godot.Collections.Dictionary dict1 = new Godot.Collections.Dictionary{{"a", 1}, {"b", 2}, {"c", 3}}; - public Godot.Collections.Dictionary dict2 = new Godot.Collections.Dictionary{{"a", 1}, {"b", 2}, {"c", 3}}; - - public void CompareDictionaries() - { - GD.Print(GD.Hash(dict1) == GD.Hash(dict2)); // Will print true. - } - [/csharp] - [/codeblocks] - [b]Note:[/b] When declaring a dictionary with [code]const[/code], the dictionary itself can still be mutated by defining the values of individual keys. Using [code]const[/code] will only prevent assigning the constant with another value after it was initialized. + [b]Note:[/b] Erasing elements while iterating over dictionaries is [b]not[/b] supported and will result in unpredictable behavior. + [b]Note:[/b] When declaring a dictionary with [code]const[/code], the dictionary becomes read-only. A read-only Dictionary's entries cannot be overriden at run-time. This does [i]not[/i] affect nested [Array] and [Dictionary] values. </description> <tutorials> <link title="GDScript basics: Dictionary">$DOCS_URL/tutorials/scripting/gdscript/gdscript_basics.html#dictionary</link> @@ -192,7 +153,7 @@ <return type="Dictionary" /> <param index="0" name="from" type="Dictionary" /> <description> - Constructs a [Dictionary] as a copy of the given [Dictionary]. + Returns the same array as [param from]. If you need a copy of the array, use [method duplicate]. </description> </constructor> </constructors> @@ -200,30 +161,30 @@ <method name="clear"> <return type="void" /> <description> - Clear the dictionary, removing all key/value pairs. + Clears the dictionary, removing all entries from it. </description> </method> <method name="duplicate" qualifiers="const"> <return type="Dictionary" /> <param index="0" name="deep" type="bool" default="false" /> <description> - Creates a copy of the dictionary, and returns it. The [param deep] parameter causes inner dictionaries and arrays to be copied recursively, but does not apply to objects. + Creates and returns a new copy of the dictionary. If [param deep] is [code]true[/code], inner [Dictionary] and [Array] keys and values are also copied, recursively. </description> </method> <method name="erase"> <return type="bool" /> <param index="0" name="key" type="Variant" /> <description> - Erase a dictionary key/value pair by key. Returns [code]true[/code] if the given key was present in the dictionary, [code]false[/code] otherwise. - [b]Note:[/b] Don't erase elements while iterating over the dictionary. You can iterate over the [method keys] array instead. + Removes the dictionary entry by key, if it exists. Returns [code]true[/code] if the given [param key] existed in the dictionary, otherwise [code]false[/code]. + [b]Note:[/b] Do not erase entries while iterating over the dictionary. You can iterate over the [method keys] array instead. </description> </method> <method name="find_key" qualifiers="const"> <return type="Variant" /> <param index="0" name="value" type="Variant" /> <description> - Returns the first key whose associated value is equal to [param value], or [code]null[/code] if no such value is found. - [b]Note:[/b] [code]null[/code] is also a valid key. If you have it in your [Dictionary], the [method find_key] method can give misleading results. + Finds and returns the first key whose associated value is equal to [param value], or [code]null[/code] if it is not found. + [b]Note:[/b] [code]null[/code] is also a valid key. If inside the dictionary, [method find_key] may give misleading results. </description> </method> <method name="get" qualifiers="const"> @@ -231,72 +192,89 @@ <param index="0" name="key" type="Variant" /> <param index="1" name="default" type="Variant" default="null" /> <description> - Returns the current value for the specified key in the [Dictionary]. If the key does not exist, the method returns the value of the optional default argument, or [code]null[/code] if it is omitted. + Returns the corresponding value for the given [param key] in the dictionary. If the [param key] does not exist, returns [param default], or [code]null[/code] if the parameter is omitted. </description> </method> <method name="has" qualifiers="const"> <return type="bool" /> <param index="0" name="key" type="Variant" /> <description> - Returns [code]true[/code] if the dictionary has a given key. - [b]Note:[/b] This is equivalent to using the [code]in[/code] operator as follows: + Returns [code]true[/code] if the dictionary contains an entry with the given [param key]. [codeblocks] [gdscript] - # Will evaluate to `true`. - if "godot" in {"godot": "engine"}: - pass + var my_dict = { + "Godot" : 4, + 210 : null, + } + + print(my_dict.has("Godot")) # Prints true + print(my_dict.has(210)) # Prints true + print(my_dict.has(4)) # Prints false [/gdscript] [csharp] - // You have to use Contains() here as an alternative to GDScript's `in` operator. - if (new Godot.Collections.Dictionary{{"godot", "engine"}}.Contains("godot")) + var myDict = new Godot.Collections.Dictionary { - // I am executed. - } + { "Godot", 4 }, + { 210, default }, + }; + + GD.Print(myDict.Contains("Godot")); // Prints true + GD.Print(myDict.Contains(210)); // Prints true + GD.Print(myDict.Contains(4)); // Prints false [/csharp] [/codeblocks] - This method (like the [code]in[/code] operator) will evaluate to [code]true[/code] as long as the key exists, even if the associated value is [code]null[/code]. + In GDScript, this is equivalent to the [code]in[/code] operator: + [codeblock] + if "Godot" in {"Godot": 4}: + print("The key is here!") # Will be printed. + [/codeblock] + [b]Note:[/b] This method returns [code]true[/code] as long as the [param key] exists, even if its corresponding value is [code]null[/code]. </description> </method> <method name="has_all" qualifiers="const"> <return type="bool" /> <param index="0" name="keys" type="Array" /> <description> - Returns [code]true[/code] if the dictionary has all the keys in the given array. + Returns [code]true[/code] if the dictionary contains all keys in the given [param keys] array. + [codeblock] + var data = {"width" : 10, "height" : 20} + data.has_all(["height", "width"]) # Returns true + [/codeblock] </description> </method> <method name="hash" qualifiers="const"> <return type="int" /> <description> - Returns a hashed 32-bit integer value representing the dictionary contents. This can be used to compare dictionaries by value: + Returns a hashed 32-bit integer value representing the dictionary contents. [codeblocks] [gdscript] - var dict1 = {0: 10} - var dict2 = {0: 10} - # The line below prints `true`, whereas it would have printed `false` if both variables were compared directly. - print(dict1.hash() == dict2.hash()) + var dict1 = {"A": 10, "B": 2} + var dict2 = {"A": 10, "B": 2} + + print(dict1.hash() == dict2.hash()) # Prints true [/gdscript] [csharp] - var dict1 = new Godot.Collections.Dictionary{{0, 10}}; - var dict2 = new Godot.Collections.Dictionary{{0, 10}}; - // The line below prints `true`, whereas it would have printed `false` if both variables were compared directly. - // Dictionary has no Hash() method. Use GD.Hash() instead. - GD.Print(GD.Hash(dict1) == GD.Hash(dict2)); + var dict1 = new Godot.Collections.Dictionary{{"A", 10}, {"B", 2}}; + var dict2 = new Godot.Collections.Dictionary{{"A", 10}, {"B", 2}}; + + // Godot.Collections.Dictionary has no Hash() method. Use GD.Hash() instead. + GD.Print(GD.Hash(dict1) == GD.Hash(dict2)); // Prints true [/csharp] [/codeblocks] - [b]Note:[/b] Dictionaries with the same keys/values but in a different order will have a different hash. - [b]Note:[/b] Dictionaries with equal content will always produce identical hash values. However, the reverse is not true. Returning identical hash values does [i]not[/i] imply the dictionaries are equal, because different dictionaries can have identical hash values due to hash collisions. + [b]Note:[/b] Dictionaries with the same entries but in a different order will not have the same hash. + [b]Note:[/b] Dictionaries with equal hash values are [i]not[/i] guaranteed to be the same, because of hash collisions. On the countrary, dictionaries with different hash values are guaranteed to be different. </description> </method> <method name="is_empty" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the dictionary is empty. + Returns [code]true[/code] if the dictionary is empty (its size is [code]0[/code]). See also [method size]. </description> </method> <method name="keys" qualifiers="const"> <return type="Array" /> <description> - Returns the list of keys in the [Dictionary]. + Returns the list of keys in the dictionary. </description> </method> <method name="merge"> @@ -304,19 +282,19 @@ <param index="0" name="dictionary" type="Dictionary" /> <param index="1" name="overwrite" type="bool" default="false" /> <description> - Adds elements from [param dictionary] to this [Dictionary]. By default, duplicate keys will not be copied over, unless [param overwrite] is [code]true[/code]. + Adds entries from [param dictionary] to this dictionary. By default, duplicate keys are not copied over, unless [param overwrite] is [code]true[/code]. </description> </method> <method name="size" qualifiers="const"> <return type="int" /> <description> - Returns the number of keys in the dictionary. + Returns the number of entries in the dictionary. Empty dictionaries ([code]{ }[/code]) always return [code]0[/code]. See also [method is_empty]. </description> </method> <method name="values" qualifiers="const"> <return type="Array" /> <description> - Returns the list of values in the [Dictionary]. + Returns the list of values in this dictionary. </description> </method> </methods> @@ -325,21 +303,22 @@ <return type="bool" /> <param index="0" name="right" type="Dictionary" /> <description> - Returns [code]true[/code] if the dictionaries differ, i.e. their key or value lists are different (including the order). + Returns [code]true[/code] if the two dictionaries do not contain the same keys and values. </description> </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="Dictionary" /> <description> - Returns [code]true[/code] if both dictionaries have the same contents, i.e. their keys list and value list are equal. + Returns [code]true[/code] if the two dictionaries contain the same keys and values. The order of the entries does not matter. + [b]Note:[/b] In C#, by convention, this operator compares by [b]reference[/b]. If you need to compare by value, iterate over both dictionaries. </description> </operator> <operator name="operator []"> <return type="Variant" /> <param index="0" name="key" type="Variant" /> <description> - Returns a value at the given [param key] or [code]null[/code] and error if the key does not exist. For safe access, use [method get] or [method has]. + Returns the corresponding value for the given [param key] in the dictionary. If the entry does not exist, fails and returns [code]null[/code]. For safe access, use [method get] or [method has]. </description> </operator> </operators> diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 0039301bf6..29135376c5 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -719,6 +719,12 @@ [b]Note:[/b] This method is implemented on macOS, Windows and Linux (X11). </description> </method> + <method name="is_touchscreen_available" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if touch events are available (Android or iOS), the capability is detected on the Webplatform or if [member ProjectSettings.input_devices/pointing/emulate_touch_from_mouse] is [code]true[/code]. + </description> + </method> <method name="keyboard_get_current_layout" qualifiers="const"> <return type="int" /> <description> @@ -889,13 +895,6 @@ Returns [code]true[/code] if the screen should never be turned off by the operating system's power-saving measures. See also [method screen_set_keep_on]. </description> </method> - <method name="screen_is_touchscreen" qualifiers="const"> - <return type="bool" /> - <param index="0" name="screen" type="int" default="-1" /> - <description> - Returns [code]true[/code] if the screen can send touch events or if [member ProjectSettings.input_devices/pointing/emulate_touch_from_mouse] is [code]true[/code]. - </description> - </method> <method name="screen_set_keep_on"> <return type="void" /> <param index="0" name="enable" type="bool" /> @@ -1156,14 +1155,14 @@ <return type="Vector2i" /> <param index="0" name="window_id" type="int" default="0" /> <description> - Returns the position of the given window to on the screen. + Returns the position of the client area of the given window on the screen. </description> </method> - <method name="window_get_real_size" qualifiers="const"> + <method name="window_get_position_with_decorations" qualifiers="const"> <return type="Vector2i" /> <param index="0" name="window_id" type="int" default="0" /> <description> - Returns the size of the window specified by [param window_id] (in pixels), including the borders drawn by the operating system. See also [method window_get_size]. + Returns the position of the given window on the screen including the borders drawn by the operating system. See also [method window_get_position]. </description> </method> <method name="window_get_safe_title_margins" qualifiers="const"> @@ -1177,7 +1176,14 @@ <return type="Vector2i" /> <param index="0" name="window_id" type="int" default="0" /> <description> - Returns the size of the window specified by [param window_id] (in pixels), excluding the borders drawn by the operating system. This is also called the "client area". See also [method window_get_real_size], [method window_set_size] and [method window_get_position]. + Returns the size of the window specified by [param window_id] (in pixels), excluding the borders drawn by the operating system. This is also called the "client area". See also [method window_get_size_with_decorations], [method window_set_size] and [method window_get_position]. + </description> + </method> + <method name="window_get_size_with_decorations" qualifiers="const"> + <return type="Vector2i" /> + <param index="0" name="window_id" type="int" default="0" /> + <description> + Returns the size of the window specified by [param window_id] (in pixels), including the borders drawn by the operating system. See also [method window_get_size]. </description> </method> <method name="window_get_vsync_mode" qualifiers="const"> @@ -1634,14 +1640,17 @@ Maximized window mode, i.e. [Window] will occupy whole screen area except task bar and still display its borders. Normally happens when the minimize button is pressed. </constant> <constant name="WINDOW_MODE_FULLSCREEN" value="3" enum="WindowMode"> - Full screen window mode. Note that this is not [i]exclusive[/i] full screen. On Windows and Linux (X11), a borderless window is used to emulate full screen. On macOS, a new desktop is used to display the running project. - Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode. + Full screen mode with full multi-window support. + Full screen window cover the entire display area of a screen, have no border or decorations. Display video mode is not changed. + [b]Note:[/b] Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode. </constant> <constant name="WINDOW_MODE_EXCLUSIVE_FULLSCREEN" value="4" enum="WindowMode"> - Exclusive full screen window mode. This mode is implemented on Windows and macOS only. On other platforms, it is equivalent to [constant WINDOW_MODE_FULLSCREEN]. - [b]On Windows:[/b] Only one window in exclusive full screen mode can be visible on a given screen at a time. If multiple windows are in exclusive full screen mode for the same screen, the last one being set to this mode takes precedence. - [b]On macOS:[/b] Exclusive full-screen mode prevents Dock and Menu from showing up when the mouse pointer is hovering the edge of the screen. - Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode. + A single window full screen mode. This mode has less overhead, but only one window can be open on a given screen at a time (opening a child window or application switching will trigger a full screen transition). + Full screen window cover the entire display area of a screen, have no border or decorations. Display video mode is not changed. + [b]On Windows:[/b] Depending on video driver, full screen transition might cause screens to go black for a moment. + [b]On macOS:[/b] Exclusive full screen mode prevents Dock and Menu from showing up when the mouse pointer is hovering the edge of the screen. + [b]On Linux (X11):[/b] Exclusive full screen mode bypasses compositor. + [b]Note:[/b] Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode. </constant> <constant name="WINDOW_FLAG_RESIZE_DISABLED" value="0" enum="WindowFlags"> The window can't be resizing by dragging its resize grip. It's still possible to resize the window using [method window_set_size]. This flag is ignored for full screen windows. @@ -1715,6 +1724,7 @@ <constant name="DISPLAY_HANDLE" value="0" enum="HandleType"> Display handle: - Linux (X11): [code]X11::Display*[/code] for the display. + - Android: [code]EGLDisplay[/code] for the display. </constant> <constant name="WINDOW_HANDLE" value="1" enum="HandleType"> Window handle: diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml index e52f897164..be0c8fd6ca 100644 --- a/doc/classes/FileAccess.xml +++ b/doc/classes/FileAccess.xml @@ -153,6 +153,20 @@ Returns the last error that happened when trying to perform operations. Compare with the [code]ERR_FILE_*[/code] constants from [enum Error]. </description> </method> + <method name="get_file_as_bytes" qualifiers="static"> + <return type="PackedByteArray" /> + <param index="0" name="path" type="String" /> + <description> + Returns the whole [param path] file contents as a [PackedByteArray] without any decoding. + </description> + </method> + <method name="get_file_as_string" qualifiers="static"> + <return type="String" /> + <param index="0" name="path" type="String" /> + <description> + Returns the whole [param path] file contents as a [String]. Text is interpreted as being UTF-8 encoded. + </description> + </method> <method name="get_float" qualifiers="const"> <return type="float" /> <description> diff --git a/doc/classes/Font.xml b/doc/classes/Font.xml index 6a42b62bcf..761e75339a 100644 --- a/doc/classes/Font.xml +++ b/doc/classes/Font.xml @@ -161,6 +161,12 @@ Returns font family name. </description> </method> + <method name="get_font_stretch" qualifiers="const"> + <return type="int" /> + <description> + Returns font stretch amount, compared to a normal width. A percentage value between [code]50%[/code] and [code]200%[/code]. + </description> + </method> <method name="get_font_style" qualifiers="const"> <return type="int" enum="TextServer.FontStyle" /> <description> @@ -173,6 +179,12 @@ Returns font style name. </description> </method> + <method name="get_font_weight" qualifiers="const"> + <return type="int" /> + <description> + Returns weight (boldness) of the font. A value in the [code]100...999[/code] range, normal font weight is [code]400[/code], bold font weight is [code]700[/code]. + </description> + </method> <method name="get_height" qualifiers="const"> <return type="float" /> <param index="0" name="font_size" type="int" default="16" /> diff --git a/doc/classes/FontFile.xml b/doc/classes/FontFile.xml index 271f6a13e0..1019c271dc 100644 --- a/doc/classes/FontFile.xml +++ b/doc/classes/FontFile.xml @@ -542,6 +542,9 @@ </method> </methods> <members> + <member name="allow_system_fallback" type="bool" setter="set_allow_system_fallback" getter="is_allow_system_fallback" default="true"> + If set to [code]true[/code], system fonts can be automatically used as fallbacks. + </member> <member name="antialiasing" type="int" setter="set_antialiasing" getter="get_antialiasing" enum="TextServer.FontAntialiasing" default="1"> Font anti-aliasing mode. </member> @@ -557,9 +560,15 @@ <member name="font_name" type="String" setter="set_font_name" getter="get_font_name" default=""""> Font family name. </member> + <member name="font_stretch" type="int" setter="set_font_stretch" getter="get_font_stretch" default="100"> + Font stretch amount, compared to a normal width. A percentage value between [code]50%[/code] and [code]200%[/code]. + </member> <member name="font_style" type="int" setter="set_font_style" getter="get_font_style" enum="TextServer.FontStyle" default="0"> Font style flags, see [enum TextServer.FontStyle]. </member> + <member name="font_weight" type="int" setter="set_font_weight" getter="get_font_weight" default="400"> + Weight (boldness) of the font. A value in the [code]100...999[/code] range, normal font weight is [code]400[/code], bold font weight is [code]700[/code]. + </member> <member name="force_autohinter" type="bool" setter="set_force_autohinter" getter="is_force_autohinter" default="false"> If set to [code]true[/code], auto-hinting is supported and preferred over font built-in hinting. Used by dynamic fonts only. </member> diff --git a/doc/classes/Light3D.xml b/doc/classes/Light3D.xml index 60e20cd97d..fe7756faaf 100644 --- a/doc/classes/Light3D.xml +++ b/doc/classes/Light3D.xml @@ -114,7 +114,7 @@ <member name="shadow_enabled" type="bool" setter="set_shadow" getter="has_shadow" default="false"> If [code]true[/code], the light will cast real-time shadows. This has a significant performance cost. Only enable shadow rendering when it makes a noticeable difference in the scene's appearance, and consider using [member distance_fade_enabled] to hide the light when far away from the [Camera3D]. </member> - <member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" default="1.0"> + <member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" default="2.0"> Offsets the lookup into the shadow map by the object's normal. This can be used to reduce self-shadowing artifacts without using [member shadow_bias]. In practice, this value should be tweaked along with [member shadow_bias] to reduce artifacts as much as possible. </member> <member name="shadow_opacity" type="float" setter="set_param" getter="get_param" default="1.0"> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 610f77e3d8..0efc6ab399 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -394,18 +394,38 @@ <method name="get_system_font_path" qualifiers="const"> <return type="String" /> <param index="0" name="font_name" type="String" /> - <param index="1" name="bold" type="bool" default="false" /> - <param index="2" name="italic" type="bool" default="false" /> + <param index="1" name="weight" type="int" default="400" /> + <param index="2" name="stretch" type="int" default="100" /> + <param index="3" name="italic" type="bool" default="false" /> <description> - Returns path to the system font file with [param font_name] and style. Return empty string if no matching fonts found. - [b]Note:[/b] This method is implemented on iOS, Linux, macOS and Windows. + Returns path to the system font file with [param font_name] and style. Returns empty string if no matching fonts found. + The following aliases can be used to request default fonts: "sans-serif", "serif", "monospace", "cursive", and "fantasy". + [b]Note:[/b] Returned font might have different style if the requested style is not available. + [b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows. + </description> + </method> + <method name="get_system_font_path_for_text" qualifiers="const"> + <return type="PackedStringArray" /> + <param index="0" name="font_name" type="String" /> + <param index="1" name="text" type="String" /> + <param index="2" name="locale" type="String" default="""" /> + <param index="3" name="script" type="String" default="""" /> + <param index="4" name="weight" type="int" default="400" /> + <param index="5" name="stretch" type="int" default="100" /> + <param index="6" name="italic" type="bool" default="false" /> + <description> + Returns an array of the system substitute font file paths, which are similar to the font with [param font_name] and style for the specified text, locale and script. Returns empty array if no matching fonts found. + The following aliases can be used to request default fonts: "sans-serif", "serif", "monospace", "cursive", and "fantasy". + [b]Note:[/b] Depending on OS, it's not guaranteed that any of the returned fonts is suitable for rendering specified text. Fonts should be loaded and checked in the order they are returned, and the first suitable one used. + [b]Note:[/b] Returned fonts might have different style if the requested style is not available or belong to a different font family. + [b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows. </description> </method> <method name="get_system_fonts" qualifiers="const"> <return type="PackedStringArray" /> <description> Returns list of font family names available. - [b]Note:[/b] This method is implemented on iOS, Linux, macOS and Windows. + [b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows. </description> </method> <method name="get_thread_caller_id" qualifiers="const"> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index bf15f96291..5e834b3d91 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -764,6 +764,22 @@ Emits the [signal property_list_changed] signal. This is mainly used to refresh the editor, so that the Inspector and editor plugins are properly updated. </description> </method> + <method name="property_can_revert" qualifiers="const"> + <return type="bool" /> + <param index="0" name="property" type="StringName" /> + <description> + Returns [code]true[/code] if the given [param property] has a custom default value. Use [method property_get_revert] to get the [param property]'s default value. + [b]Note:[/b] This method is used by the Inspector dock to display a revert icon. The object must implement [method _property_can_revert] to customize the default value. If [method _property_can_revert] is not implemented, this method returns [code]false[/code]. + </description> + </method> + <method name="property_get_revert" qualifiers="const"> + <return type="Variant" /> + <param index="0" name="property" type="StringName" /> + <description> + Returns the custom default value of the given [param property]. Use [method property_can_revert] to check if the [param property] has a custom default value. + [b]Note:[/b] This method is used by the Inspector dock to display a revert icon. The object must implement [method _property_get_revert] to customize the default value. If [method _property_get_revert] is not implemented, this method returns [code]null[/code]. + </description> + </method> <method name="remove_meta"> <return type="void" /> <param index="0" name="name" type="StringName" /> diff --git a/doc/classes/OmniLight3D.xml b/doc/classes/OmniLight3D.xml index 193ae8deeb..f71c81e713 100644 --- a/doc/classes/OmniLight3D.xml +++ b/doc/classes/OmniLight3D.xml @@ -20,7 +20,7 @@ <member name="omni_shadow_mode" type="int" setter="set_shadow_mode" getter="get_shadow_mode" enum="OmniLight3D.ShadowMode" default="1"> See [enum ShadowMode]. </member> - <member name="shadow_bias" type="float" setter="set_param" getter="get_param" overrides="Light3D" default="0.2" /> + <member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" overrides="Light3D" default="1.0" /> </members> <constants> <constant name="SHADOW_DUAL_PARABOLOID" value="0" enum="ShadowMode"> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 1f0a8d91fa..dce9d5a55c 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -2299,6 +2299,9 @@ <member name="xr/openxr/reference_space" type="int" setter="" getter="" default=""1""> Specify the default reference space. </member> + <member name="xr/openxr/submit_depth_buffer" type="bool" setter="" getter="" default="false"> + If [code]true[/code], OpenXR will manage the depth buffer and use the depth buffer for advanced reprojection provided this is supported by the XR runtime. Note that some rendering features in Godot can't be used with this feature. + </member> <member name="xr/openxr/view_configuration" type="int" setter="" getter="" default=""1""> Specify the view configuration with which to configure OpenXR setting up either Mono or Stereo rendering. </member> diff --git a/doc/classes/RID.xml b/doc/classes/RID.xml index a6523e4c8b..2d40a7be57 100644 --- a/doc/classes/RID.xml +++ b/doc/classes/RID.xml @@ -4,7 +4,8 @@ Handle for a [Resource]'s unique ID. </brief_description> <description> - The RID type is used to access the unique integer ID of a resource. They are opaque, which means they do not grant access to the associated resource by themselves. They are used by and with the low-level Server classes such as [RenderingServer]. + The RID [Variant] type is used to access a low-level resource by its unique ID. RIDs are opaque, which means they do not grant access to the resource by themselves. They are used by the low-level server classes, such as [DisplayServer], [RenderingServer], [TextServer], etc. + A low-level resource may correspond to a high-level [Resource], such as [Texture] or [Mesh]. </description> <tutorials> </tutorials> @@ -27,13 +28,13 @@ <method name="get_id" qualifiers="const"> <return type="int" /> <description> - Returns the ID of the referenced resource. + Returns the ID of the referenced low-level resource. </description> </method> <method name="is_valid" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if [RID] is valid. + Returns [code]true[/code] if the [RID] is not [code]0[/code]. </description> </method> </methods> @@ -42,36 +43,42 @@ <return type="bool" /> <param index="0" name="right" type="RID" /> <description> + Returns [code]true[/code] if the [RID]s are not equal. </description> </operator> <operator name="operator <"> <return type="bool" /> <param index="0" name="right" type="RID" /> <description> + Returns [code]true[/code] if the [RID]'s ID is less than [param right]'s ID. </description> </operator> <operator name="operator <="> <return type="bool" /> <param index="0" name="right" type="RID" /> <description> + Returns [code]true[/code] if the [RID]'s ID is less than or equal to [param right]'s ID. </description> </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="RID" /> <description> + Returns [code]true[/code] if both [RID]s are equal, which means they both refer to the same low-level resource. </description> </operator> <operator name="operator >"> <return type="bool" /> <param index="0" name="right" type="RID" /> <description> + Returns [code]true[/code] if the [RID]'s ID is greater than [param right]'s ID. </description> </operator> <operator name="operator >="> <return type="bool" /> <param index="0" name="right" type="RID" /> <description> + Returns [code]true[/code] if the [RID]'s ID is greater than or equal to [param right]'s ID. </description> </operator> </operators> diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index cb2481f705..e222894647 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -399,6 +399,12 @@ Scrolls the window's top line to match first line of the [param paragraph]. </description> </method> + <method name="scroll_to_selection"> + <return type="void" /> + <description> + Scrolls to the beginning of the current selection. + </description> + </method> <method name="select_all"> <return type="void" /> <description> diff --git a/doc/classes/SpotLight3D.xml b/doc/classes/SpotLight3D.xml index 28e2ef0d95..59d36aefab 100644 --- a/doc/classes/SpotLight3D.xml +++ b/doc/classes/SpotLight3D.xml @@ -12,6 +12,7 @@ </tutorials> <members> <member name="shadow_bias" type="float" setter="set_param" getter="get_param" overrides="Light3D" default="0.03" /> + <member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" overrides="Light3D" default="1.0" /> <member name="spot_angle" type="float" setter="set_param" getter="get_param" default="45.0"> The spotlight's angle in degrees. [b]Note:[/b] [member spot_angle] is not affected by [member Node3D.scale] (the light's scale or its parent's scale). diff --git a/doc/classes/String.xml b/doc/classes/String.xml index c186952c74..7e76a134ff 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -1,10 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="String" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Built-in string class. + Built-in string Variant type. </brief_description> <description> - This is the built-in string class (and the one used by GDScript). It supports Unicode and provides all necessary means for string handling. Strings are reference-counted and use a copy-on-write approach, so passing them around is cheap in resources. + This is the built-in string Variant type (and the one used by GDScript). Strings may contain any number of Unicode characters, and expose methods useful for manipulating and generating strings. Strings are reference-counted and use a copy-on-write approach (every modification to a string returns a new [String]), so passing them around is cheap in resources. + Some string methods have corresponding variations. Variations suffixed with [code]n[/code] ([method countn], [method findn], [method replacen], etc.) are [b]case-insensitive[/b] (they make no distinction between uppercase and lowercase letters). Method variations prefixed with [code]r[/code] ([method rfind], [method rsplit], etc.) are reversed, and start from the end of the string, instead of the beginning. + [b]Note:[/b] In a boolean context, a string will evaluate to [code]false[/code] if it is empty ([code]""[/code]). Otherwise, a string will always evaluate to [code]true[/code]. </description> <tutorials> <link title="GDScript format strings">$DOCS_URL/tutorials/scripting/gdscript/gdscript_format_string.html</link> @@ -43,30 +45,32 @@ <return type="bool" /> <param index="0" name="text" type="String" /> <description> - Returns [code]true[/code] if the string begins with the given string. + Returns [code]true[/code] if the string begins with the given [param text]. See also [method ends_with]. </description> </method> <method name="bigrams" qualifiers="const"> <return type="PackedStringArray" /> <description> - Returns an array containing the bigrams (pairs of consecutive letters) of this string. + Returns an array containing the bigrams (pairs of consecutive characters) of this string. [codeblock] - print("Bigrams".bigrams()) # Prints ["Bi", "ig", "gr", "ra", "am", "ms"] + print("Get up!".bigrams()) # Prints ["Ge", "et", "t ", " u", "up", "p!"] [/codeblock] </description> </method> <method name="bin_to_int" qualifiers="const"> <return type="int" /> <description> - Converts a string containing a binary number into an integer. Binary strings can either be prefixed with [code]0b[/code] or not, and they can also start with a [code]-[/code] before the optional prefix. + Converts the string representing a binary number into an [int]. The string may optionally be prefixed with [code]"0b"[/code], and an additional [code]-[/code] prefix for negative numbers. [codeblocks] [gdscript] - print("0b101".bin_to_int()) # Prints "5". - print("101".bin_to_int()) # Prints "5". + print("101".bin_to_int()) # Prints 5 + print("0b101".bin_to_int()) # Prints 5 + print("-0b10".bin_to_int()) # Prints -2 [/gdscript] [csharp] - GD.Print("0b101".BinToInt()); // Prints "5". - GD.Print("101".BinToInt()); // Prints "5". + GD.Print("101".BinToInt()); // Prints 5 + GD.Print("0b101".BinToInt()); // Prints 5 + GD.Print("-0b10".BinToInt()); // Prints -2 [/csharp] [/codeblocks] </description> @@ -87,26 +91,36 @@ <method name="capitalize" qualifiers="const"> <return type="String" /> <description> - Changes the case of some letters. Replaces underscores with spaces, adds spaces before in-word uppercase characters, converts all letters to lowercase, then capitalizes the first letter and every letter following a space character. For [code]capitalize camelCase mixed_with_underscores[/code], it will return [code]Capitalize Camel Case Mixed With Underscores[/code]. + Changes the appearance of the string: replaces underscores ([code]_[/code]) with spaces, adds spaces before uppercase letters in the middle of a word, converts all letters to lowercase, then converts the first one and each one following a space to uppercase. + [codeblocks] + [gdscript] + "move_local_x".capitalize() # Returns "Move Local X" + "sceneFile_path".capitalize() # Returns "Scene File Path" + [/gdscript] + [csharp] + "move_local_x".Capitalize(); // Returns "Move Local X" + "sceneFile_path".Capitalize(); // Returns "Scene File Path" + [/csharp] + [/codeblocks] + [b]Note:[/b] This method not the same as the default appearance of properties in the Inspector dock, as it does not capitalize acronyms ([code]"2D"[/code], [code]"FPS"[/code], [code]"PNG"[/code], etc.) as you may expect. </description> </method> <method name="casecmp_to" qualifiers="const"> <return type="int" /> <param index="0" name="to" type="String" /> <description> - Performs a case-sensitive comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. - [b]Behavior with different string lengths:[/b] Returns [code]1[/code] if the "base" string is longer than the [param to] string or [code]-1[/code] if the "base" string is shorter than the [param to] string. Keep in mind this length is determined by the number of Unicode codepoints, [i]not[/i] the actual visible characters. - [b]Behavior with empty strings:[/b] Returns [code]-1[/code] if the "base" string is empty, [code]1[/code] if the [param to] string is empty or [code]0[/code] if both strings are empty. - To get a boolean result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to] and [method naturalnocasecmp_to]. + Performs a case-sensitive comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" and "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. + With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code]. + To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to] and [method naturalnocasecmp_to]. </description> </method> <method name="chr" qualifiers="static"> <return type="String" /> <param index="0" name="char" type="int" /> <description> - Directly converts an decimal integer to a unicode character. Tables of these characters can be found in various locations, for example [url=https://unicodelookup.com/]here.[/url] + Returns a single Unicode character from the decimal [param char]. You may use [url=https://unicodelookup.com/]unicodelookup.com[/url] or [url=https://www.unicode.org/charts/]unicode.org[/url] as points of reference. [codeblock] - print(String.chr(65)) # Prints "A" + print(String.chr(65)) # Prints "A" print(String.chr(129302)) # Prints "🤖" (robot face emoji) [/codeblock] </description> @@ -115,7 +129,19 @@ <return type="bool" /> <param index="0" name="what" type="String" /> <description> - Returns [code]true[/code] if the string contains the given string. + Returns [code]true[/code] if the string contains [param what]. In GDScript, this corresponds to the [code]in[/code] operator. + [codeblocks] + [gdscript] + print("Node".contains("de")) # Prints true + print("team".contains("I")) # Prints false + print("I" in "team") # Prints false + [/gdscript] + [csharp] + GD.Print("Node".Contains("de")); // Prints true + GD.Print("team".Contains("I")); // Prints false + [/csharp] + [/codeblocks] + If you need to know where [param what] is within the string, use [method find]. </description> </method> <method name="count" qualifiers="const"> @@ -124,7 +150,7 @@ <param index="1" name="from" type="int" default="0" /> <param index="2" name="to" type="int" default="0" /> <description> - Returns the number of occurrences of substring [param what] between [param from] and [param to] positions. If [param from] and [param to] equals 0 the whole string will be used. If only [param to] equals 0 the remained substring will be used. + Returns the number of occurrences of the substring [param what] between [param from] and [param to] positions. If [param to] is 0, the search continues until the end of the string. </description> </method> <method name="countn" qualifiers="const"> @@ -133,7 +159,7 @@ <param index="1" name="from" type="int" default="0" /> <param index="2" name="to" type="int" default="0" /> <description> - Returns the number of occurrences of substring [param what] (ignoring case) between [param from] and [param to] positions. If [param from] and [param to] equals 0 the whole string will be used. If only [param to] equals 0 the remained substring will be used. + Returns the number of occurrences of the substring [param what] between [param from] and [param to] positions, [b]ignoring case[/b]. If [param to] is 0, the search continues until the end of the string. </description> </method> <method name="dedent" qualifiers="const"> @@ -146,7 +172,7 @@ <return type="bool" /> <param index="0" name="text" type="String" /> <description> - Returns [code]true[/code] if the string ends with the given string. + Returns [code]true[/code] if the string ends with the given [param text]. See also [method begins_with]. </description> </method> <method name="find" qualifiers="const"> @@ -154,17 +180,24 @@ <param index="0" name="what" type="String" /> <param index="1" name="from" type="int" default="0" /> <description> - Returns the index of the [b]first[/b] case-sensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the end of the string. - [b]Note:[/b] If you just want to know whether a string contains a substring, use the [code]in[/code] operator as follows: + Returns the index of the [b]first[/b] occurrence of [param what] in this string, or [code]-1[/code] if there are none. The search's start can be specified with [param from], continuing to the end of the string. [codeblocks] [gdscript] - print("i" in "team") # Will print `false`. + print("Team".find("I")) # Prints -1 + + print("Potato".find("t")) # Prints 2 + print("Potato".find("t", 3)) # Prints 4 + print("Potato".find("t", 5)) # Prints -1 [/gdscript] [csharp] - // C# has no in operator, but we can use `Contains()`. - GD.Print("team".Contains("i")); // Will print `false`. + GD.Print("Team".Find("I")); // Prints -1 + + GD.Print("Potato".Find("t")); // Prints 2 + GD.print("Potato".Find("t", 3)); // Prints 4 + GD.print("Potato".Find("t", 5)); // Prints -1 [/csharp] [/codeblocks] + [b]Note:[/b] If you just want to know whether the string contains [param what], use [method contains]. In GDScript, you may also use the [code]in[/code] operator. </description> </method> <method name="findn" qualifiers="const"> @@ -172,7 +205,7 @@ <param index="0" name="what" type="String" /> <param index="1" name="from" type="int" default="0" /> <description> - Returns the index of the [b]first[/b] case-insensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the end of the string. + Returns the index of the [b]first[/b] [b]case-insensitive[/b] occurrence of [param what] in this string, or [code]-1[/code] if there are none. The starting search index can be specified with [param from], continuing to the end of the string. </description> </method> <method name="format" qualifiers="const"> @@ -183,53 +216,64 @@ Formats the string by replacing all occurrences of [param placeholder] with the elements of [param values]. [param values] can be a [Dictionary] or an [Array]. Any underscores in [param placeholder] will be replaced with the corresponding keys in advance. Array elements use their index as keys. [codeblock] - # Prints: Waiting for Godot is a play by Samuel Beckett, and Godot Engine is named after it. + # Prints "Waiting for Godot is a play by Samuel Beckett, and Godot Engine is named after it." var use_array_values = "Waiting for {0} is a play by {1}, and {0} Engine is named after it." print(use_array_values.format(["Godot", "Samuel Beckett"])) - # Prints: User 42 is Godot. + # Prints "User 42 is Godot." print("User {id} is {name}.".format({"id": 42, "name": "Godot"})) [/codeblock] - Some additional handling is performed when [param values] is an array. If [param placeholder] does not contain an underscore, the elements of the array will be used to replace one occurrence of the placeholder in turn; If an array element is another 2-element array, it'll be interpreted as a key-value pair. + Some additional handling is performed when [param values] is an [Array]. If [param placeholder] does not contain an underscore, the elements of the [param values] array will be used to replace one occurrence of the placeholder in order; If an element of [param values] is another 2-element array, it'll be interpreted as a key-value pair. [codeblock] - # Prints: User 42 is Godot. + # Prints "User 42 is Godot." print("User {} is {}.".format([42, "Godot"], "{}")) print("User {id} is {name}.".format([["id", 42], ["name", "Godot"]])) [/codeblock] + See also the [url=$DOCS_URL/tutorials/scripting/gdscript/gdscript_format_string.html]GDScript format string[/url] tutorial. </description> </method> <method name="get_base_dir" qualifiers="const"> <return type="String" /> <description> If the string is a valid file path, returns the base directory name. + [codeblock] + var dir_path = "/path/to/file.txt".get_basename() # dir_path is "/path/to" + [/codeblock] </description> </method> <method name="get_basename" qualifiers="const"> <return type="String" /> <description> - If the string is a valid file path, returns the full file path without the extension. + If the string is a valid file path, returns the full file path, without the extension. + [codeblock] + var base = "/path/to/file.txt".get_basename() # base is "/path/to/file" + [/codeblock] </description> </method> <method name="get_extension" qualifiers="const"> <return type="String" /> <description> - Returns the extension without the leading period character ([code].[/code]) if the string is a valid file name or path. If the string does not contain an extension, returns an empty string instead. + If the string is a valid file name or path, returns the file extension without the leading period ([code].[/code]). Otherwise, returns an empty string. [codeblock] - print("/path/to/file.txt".get_extension()) # "txt" - print("file.txt".get_extension()) # "txt" - print("file.sample.txt".get_extension()) # "txt" - print(".txt".get_extension()) # "txt" - print("file.txt.".get_extension()) # "" (empty string) - print("file.txt..".get_extension()) # "" (empty string) - print("txt".get_extension()) # "" (empty string) - print("".get_extension()) # "" (empty string) + var a = "/path/to/file.txt".get_extension() # a is "txt" + var b = "cool.txt".get_extension() # b is "txt" + var c = "cool.font.tres".get_extension() # c is "tres" + var d = ".pack1".get_extension() # d is "pack1" + + var e = "file.txt.".get_extension() # e is "" + var f = "file.txt..".get_extension() # f is "" + var g = "txt".get_extension() # g is "" + var h = "".get_extension() # h is "" [/codeblock] </description> </method> <method name="get_file" qualifiers="const"> <return type="String" /> <description> - If the string is a valid file path, returns the filename. + If the string is a valid file path, returns the file name, including the extension. + [codeblock] + var file = "/path/to/icon.png".get_file() # file is "icon.png" + [/codeblock] </description> </method> <method name="get_slice" qualifiers="const"> @@ -237,11 +281,11 @@ <param index="0" name="delimiter" type="String" /> <param index="1" name="slice" type="int" /> <description> - Splits a string using a [param delimiter] and returns a substring at index [param slice]. Returns an empty string if the index doesn't exist. - This is a more performant alternative to [method split] for cases when you need only one element from the array at a fixed index. + Splits the string using a [param delimiter] and returns the substring at index [param slice]. Returns an empty string if the [param slice] does not exist. + This is faster than [method split], if you only need one substring. [b]Example:[/b] [codeblock] - print("i/am/example/string".get_slice("/", 2)) # Prints 'example'. + print("i/am/example/hi".get_slice("/", 2)) # Prints "example" [/codeblock] </description> </method> @@ -249,7 +293,7 @@ <return type="int" /> <param index="0" name="delimiter" type="String" /> <description> - Splits a string using a [param delimiter] and returns a number of slices. + Returns the total number of slices when the string is split with the given [param delimiter] (see [method split]). </description> </method> <method name="get_slicec" qualifiers="const"> @@ -257,29 +301,29 @@ <param index="0" name="delimiter" type="int" /> <param index="1" name="slice" type="int" /> <description> - Splits a string using a Unicode character with code [param delimiter] and returns a substring at index [param slice]. Returns an empty string if the index doesn't exist. - This is a more performant alternative to [method split] for cases when you need only one element from the array at a fixed index. + Splits the string using a Unicode character with code [param delimiter] and returns the substring at index [param slice]. Returns an empty string if the [param slice] does not exist. + This is faster than [method split], if you only need one substring. </description> </method> <method name="hash" qualifiers="const"> <return type="int" /> <description> Returns the 32-bit hash value representing the string's contents. - [b]Note:[/b] [String]s with equal content will always produce identical hash values. However, the reverse is not true. Returning identical hash values does [i]not[/i] imply the strings are equal, because different strings can have identical hash values due to hash collisions. + [b]Note:[/b] Strings with equal hash values are [i]not[/i] guaranteed to be the same, as a result of hash collisions. On the countrary, strings with different hash values are guaranteed to be different. </description> </method> <method name="hex_to_int" qualifiers="const"> <return type="int" /> <description> - Converts a string containing a hexadecimal number into an integer. Hexadecimal strings can either be prefixed with [code]0x[/code] or not, and they can also start with a [code]-[/code] before the optional prefix. + Converts the string representing a hexadecimal number into an [int]. The string may be optionally prefixed with [code]"0x"[/code], and an additional [code]-[/code] prefix for negative numbers. [codeblocks] [gdscript] - print("0xff".hex_to_int()) # Prints "255". - print("ab".hex_to_int()) # Prints "171". + print("0xff".hex_to_int()) # Prints 255 + print("ab".hex_to_int()) # Prints 171 [/gdscript] [csharp] - GD.Print("0xff".HexToInt()); // Prints "255". - GD.Print("ab".HexToInt()); // Prints "171". + GD.Print("0xff".HexToInt()); // Prints 255 + GD.Print("ab".HexToInt()); // Prints 171 [/csharp] [/codeblocks] </description> @@ -288,17 +332,16 @@ <return type="String" /> <param index="0" name="size" type="int" /> <description> - Converts an integer representing a number of bytes into a human-readable form. - Note that this output is in [url=https://en.wikipedia.org/wiki/Binary_prefix#IEC_prefixes]IEC prefix format[/url], and includes [code]B[/code], [code]KiB[/code], [code]MiB[/code], [code]GiB[/code], [code]TiB[/code], [code]PiB[/code], and [code]EiB[/code]. + Converts [param size] which represents a number of bytes into a human-readable form. + The result is in [url=https://en.wikipedia.org/wiki/Binary_prefix#IEC_prefixes]IEC prefix format[/url], which may end in either [code]"B"[/code], [code]"KiB"[/code], [code]"MiB"[/code], [code]"GiB"[/code], [code]"TiB"[/code], [code]"PiB"[/code], or [code]"EiB"[/code]. </description> </method> <method name="indent" qualifiers="const"> <return type="String" /> <param index="0" name="prefix" type="String" /> <description> - Returns a copy of the string with lines indented with [param prefix]. - For example, the string can be indented with two tabs using [code]"\t\t"[/code], or four spaces using [code]" "[/code]. The prefix can be any string so it can also be used to comment out strings with e.g. [code]"# "[/code]. See also [method dedent] to remove indentation. - [b]Note:[/b] Empty lines are kept empty. + Indents every line of the string with the given [param prefix]. Empty lines are not indented. See also [method dedent] to remove indentation. + For example, the string can be indented with two tabulations using [code]"\t\t"[/code], or four spaces using [code]" "[/code]. </description> </method> <method name="insert" qualifiers="const"> @@ -306,57 +349,65 @@ <param index="0" name="position" type="int" /> <param index="1" name="what" type="String" /> <description> - Returns a copy of the string with the substring [param what] inserted at the given [param position]. + Inserts [param what] at the given [param position] in the string. </description> </method> <method name="is_absolute_path" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the string is a path to a file or directory and its starting point is explicitly defined. This includes [code]res://[/code], [code]user://[/code], [code]C:\[/code], [code]/[/code], etc. + Returns [code]true[/code] if the string is a path to a file or directory, and its starting point is explicitly defined. This method is the opposite of [method is_relative_path]. + This includes all paths starting with [code]"res://"[/code], [code]"user://"[/code], [code]"C:\"[/code], [code]"/"[/code], etc. </description> </method> <method name="is_empty" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the length of the string equals [code]0[/code]. + Returns [code]true[/code] if the string's length is [code]0[/code] ([code]""[/code]). See also [method length]. </description> </method> <method name="is_relative_path" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the string is a path to a file or directory and its starting point is implicitly defined within the context it is being used. The starting point may refer to the current directory ([code]./[/code]), or the current [Node]. + Returns [code]true[/code] if the string is a path, and its starting point is dependent on context. The path could begin from the current directory, or the current [Node] (if the string is derived from a [NodePath]), and may sometimes be prefixed with [code]"./"[/code]. This method is the opposite of [method is_absolute_path]. </description> </method> <method name="is_subsequence_of" qualifiers="const"> <return type="bool" /> <param index="0" name="text" type="String" /> <description> - Returns [code]true[/code] if this string is a subsequence of the given string. + Returns [code]true[/code] if all characters of this string can be found in [param text] in their original order. + [codeblock] + var text = "Wow, incredible!" + + print("inedible".is_subsequence_of(text)) # Prints true + print("Word!".is_subsequence_of(text)) # Prints true + print("Window".is_subsequence_of(text)) # Prints false + print("".is_subsequence_of(text)) # Prints true + [/codeblock] </description> </method> <method name="is_subsequence_ofn" qualifiers="const"> <return type="bool" /> <param index="0" name="text" type="String" /> <description> - Returns [code]true[/code] if this string is a subsequence of the given string, without considering case. + Returns [code]true[/code] if all characters of this string can be found in [param text] in their original order, [b]ignoring case[/b]. </description> </method> <method name="is_valid_filename" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string is free from characters that aren't allowed in file names, those being: - [code]: / \ ? * " | % < >[/code] + Returns [code]true[/code] if this string does not contain characters that are not allowed in file names ([code]:[/code] [code]/[/code] [code]\[/code] [code]?[/code] [code]*[/code] [code]"[/code] [code]|[/code] [code]%[/code] [code]<[/code] [code]>[/code]). </description> </method> <method name="is_valid_float" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string contains a valid float. This is inclusive of integers, and also supports exponents: + Returns [code]true[/code] if this string represents a valid floating-point number. A valid float may contain only digits, one decimal point ([code].[/code]), and the exponent letter ([code]e[/code]). It may also be prefixed with a positive ([code]+[/code]) or negative ([code]-[/code]) sign. Any valid integer is also a valid float (see [method is_valid_int]). See also [method to_float]. [codeblock] - print("1.7".is_valid_float()) # Prints "true" - print("24".is_valid_float()) # Prints "true" - print("7e3".is_valid_float()) # Prints "true" - print("Hello".is_valid_float()) # Prints "false" + print("1.7".is_valid_float()) # Prints true + print("24".is_valid_float()) # Prints true + print("7e3".is_valid_float()) # Prints true + print("Hello".is_valid_float()) # Prints false [/codeblock] </description> </method> @@ -364,57 +415,73 @@ <return type="bool" /> <param index="0" name="with_prefix" type="bool" default="false" /> <description> - Returns [code]true[/code] if this string contains a valid hexadecimal number. If [param with_prefix] is [code]true[/code], then a validity of the hexadecimal number is determined by the [code]0x[/code] prefix, for example: [code]0xDEADC0DE[/code]. + Returns [code]true[/code] if this string is a valid hexadecimal number. A valid hexadecimal number only contains digits or letters [code]A[/code] to [code]F[/code] (either uppercase or lowercase), and may be prefixed with a positive ([code]+[/code]) or negative ([code]-[/code]) sign. + If [param with_prefix] is [code]true[/code], the hexadecimal number needs to prefixed by [code]"0x"[/code] to be considered valid. + [codeblock] + print("A08E".is_valid_hex_number()) # Prints true + print("-AbCdEf".is_valid_hex_number()) # Prints true + print("2.5".is_valid_hex_number()) # Prints false + + print("0xDEADC0DE".is_valid_hex_number(true)) # Prints true + [/codeblock] </description> </method> <method name="is_valid_html_color" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string contains a valid color in hexadecimal HTML notation. Other HTML notations such as named colors or [code]hsl()[/code] colors aren't considered valid by this method and will return [code]false[/code]. + Returns [code]true[/code] if this string is a valid color in hexadecimal HTML notation. The string must be a hexadecimal value (see [method is_valid_hex_number]) of either 3, 4, 6 or 8 digits, and may be prefixed by a hash sign ([code]#[/code]). Other HTML notations for colors, such as names or [code]hsl()[/code], are not considered valid. See also [method Color.html]. </description> </method> <method name="is_valid_identifier" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string is a valid identifier. A valid identifier may contain only letters, digits and underscores ([code]_[/code]) and the first character may not be a digit. + Returns [code]true[/code] if this string is a valid identifier. A valid identifier may contain only letters, digits and underscores ([code]_[/code]), and the first character may not be a digit. [codeblock] - print("good_ident_1".is_valid_identifier()) # Prints "true" - print("1st_bad_ident".is_valid_identifier()) # Prints "false" - print("bad_ident_#2".is_valid_identifier()) # Prints "false" + print("node_2d".is_valid_identifier()) # Prints true + print("TYPE_FLOAT".is_valid_identifier()) # Prints true + print("1st_method".is_valid_identifier()) # Prints false + print("MyMethod#2".is_valid_identifier()) # Prints false [/codeblock] </description> </method> <method name="is_valid_int" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string contains a valid integer. + Returns [code]true[/code] if this string represents a valid integer. A valid integer only contains digits, and may be prefixed with a positive ([code]+[/code]) or negative ([code]-[/code]) sign. See also [method to_int]. [codeblock] - print("7".is_valid_int()) # Prints "true" - print("14.6".is_valid_int()) # Prints "false" - print("L".is_valid_int()) # Prints "false" - print("+3".is_valid_int()) # Prints "true" - print("-12".is_valid_int()) # Prints "true" + print("7".is_valid_int()) # Prints true + print("1.65".is_valid_int()) # Prints false + print("Hi".is_valid_int()) # Prints false + print("+3".is_valid_int()) # Prints true + print("-12".is_valid_int()) # Prints true [/codeblock] </description> </method> <method name="is_valid_ip_address" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string contains only a well-formatted IPv4 or IPv6 address. This method considers [url=https://en.wikipedia.org/wiki/Reserved_IP_addresses]reserved IP addresses[/url] such as [code]0.0.0.0[/code] as valid. + Returns [code]true[/code] if this string represents a well-formatted IPv4 or IPv6 address. This method considers [url=https://en.wikipedia.org/wiki/Reserved_IP_addresses]reserved IP addresses[/url] such as [code]"0.0.0.0"[/code] and [code]"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"[/code] as valid. </description> </method> <method name="join" qualifiers="const"> <return type="String" /> <param index="0" name="parts" type="PackedStringArray" /> <description> - Returns a [String] which is the concatenation of the [param parts]. The separator between elements is the string providing this method. + Returns the concatenation of [param parts]' elements, with each element separated by the string calling this method. This method is the opposite of [method split]. [b]Example:[/b] [codeblocks] [gdscript] - print(", ".join(["One", "Two", "Three", "Four"])) + var fruits = ["Apple", "Orange", "Pear", "Kiwi"] + + print(", ".join(fruits)) # Prints "Apple, Orange, Pear, Kiwi" + print("---".join(fruits)) # Prints "Apple---Orange---Pear---Kiwi" [/gdscript] [csharp] - GD.Print(String.Join(",", new string[] {"One", "Two", "Three", "Four"})); + var fruits = new string[] {"Apple", "Orange", "Pear", "Kiwi"}; + + // In C#, this method is static. + GD.Print(string.Join(", ", fruits); // Prints "Apple, Orange, Pear, Kiwi" + GD.Print(string.Join("---", fruits)); // Prints "Apple---Orange---Pear---Kiwi" [/csharp] [/codeblocks] </description> @@ -422,25 +489,24 @@ <method name="json_escape" qualifiers="const"> <return type="String" /> <description> - Returns a copy of the string with special characters escaped using the JSON standard. + Returns a copy of the string with special characters escaped using the JSON standard. Because it closely matches the C standard, it is possible to use [method c_unescape] to unescape the string, if necessary. </description> </method> <method name="left" qualifiers="const"> <return type="String" /> <param index="0" name="length" type="int" /> <description> - Returns a number of characters from the left of the string. If negative [param length] is used, the characters are counted downwards from [String]'s length. - [b]Example:[/b] + Returns the first [param length] characters from the beginning of the string. If [param length] is negative, strips the last [param length] characters from the string's end. [codeblock] - print("sample text".left(3)) #prints "sam" - print("sample text".left(-3)) #prints "sample t" + print("Hello World!".left(3)) # Prints "Hel" + print("Hello World!".left(-4)) # Prints "Hello Wo" [/codeblock] </description> </method> <method name="length" qualifiers="const"> <return type="int" /> <description> - Returns the number of characters in the string. + Returns the number of characters in the string. Empty strings ([code]""[/code]) always return [code]0[/code]. See also [method is_empty]. </description> </method> <method name="lpad" qualifiers="const"> @@ -448,62 +514,60 @@ <param index="0" name="min_length" type="int" /> <param index="1" name="character" type="String" default="" "" /> <description> - Formats a string to be at least [param min_length] long by adding [param character]s to the left of the string. + Formats the string to be at least [param min_length] long by adding [param character]s to the left of the string, if necessary. See also [method rpad]. </description> </method> <method name="lstrip" qualifiers="const"> <return type="String" /> <param index="0" name="chars" type="String" /> <description> - Returns a copy of the string with characters removed from the left. The [param chars] argument is a string specifying the set of characters to be removed. - [b]Note:[/b] The [param chars] is not a prefix. See [method trim_prefix] method that will remove a single prefix string rather than a set of characters. + Removes a set of characters defined in [param chars] from the string's beginning. See also [method rstrip]. + [b]Note:[/b] [param chars] is not a prefix. Use [method trim_prefix] to remove a single prefix, rather than a set of characters. </description> </method> <method name="match" qualifiers="const"> <return type="bool" /> <param index="0" name="expr" type="String" /> <description> - Does a simple case-sensitive expression match, where [code]"*"[/code] matches zero or more arbitrary characters and [code]"?"[/code] matches any single character except a period ([code]"."[/code]). An empty string or empty expression always evaluates to [code]false[/code]. + Does a simple expression match, where [code]*[/code] matches zero or more arbitrary characters and [code]?[/code] matches any single character except a period ([code].[/code]). An empty string or empty expression always evaluates to [code]false[/code]. </description> </method> <method name="matchn" qualifiers="const"> <return type="bool" /> <param index="0" name="expr" type="String" /> <description> - Does a simple case-insensitive expression match, where [code]"*"[/code] matches zero or more arbitrary characters and [code]"?"[/code] matches any single character except a period ([code]"."[/code]). An empty string or empty expression always evaluates to [code]false[/code]. + Does a simple [b]case-insensitive[/b] expression match, where [code]*[/code] matches zero or more arbitrary characters and [code]?[/code] matches any single character except a period ([code].[/code]). An empty string or empty expression always evaluates to [code]false[/code]. </description> </method> <method name="md5_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Returns the MD5 hash of the string as an array of bytes. + Returns the [url=https://en.wikipedia.org/wiki/MD5]MD5 hash[/url] of the string as a [PackedByteArray]. </description> </method> <method name="md5_text" qualifiers="const"> <return type="String" /> <description> - Returns the MD5 hash of the string as a string. + Returns the [url=https://en.wikipedia.org/wiki/MD5]MD5 hash[/url] of the string as another [String]. </description> </method> <method name="naturalnocasecmp_to" qualifiers="const"> <return type="int" /> <param index="0" name="to" type="String" /> <description> - Performs a case-insensitive [i]natural order[/i] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters will be converted to uppercase during the comparison. - When used for sorting, natural order comparison will order suites of numbers as expected by most people. If you sort the numbers from 1 to 10 using natural order, you will get [code][1, 2, 3, ...][/code] instead of [code][1, 10, 2, 3, ...][/code]. - [b]Behavior with different string lengths:[/b] Returns [code]1[/code] if the "base" string is longer than the [param to] string or [code]-1[/code] if the "base" string is shorter than the [param to] string. Keep in mind this length is determined by the number of Unicode codepoints, [i]not[/i] the actual visible characters. - [b]Behavior with empty strings:[/b] Returns [code]-1[/code] if the "base" string is empty, [code]1[/code] if the [param to] string is empty or [code]0[/code] if both strings are empty. - To get a boolean result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to] and [method casecmp_to]. + Performs a [b]case-insensitive[/b], [i]natural order[/i] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters are converted to uppercase for the comparison. + When used for sorting, natural order comparison orders sequences of numbers by the combined value of each digit as is often expected, instead of the single digit's value. A sorted sequence of numbered strings will be [code]["1", "2", "3", ...][/code], not [code]["1", "10", "2", "3", ...][/code]. + With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code]. + To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to] and [method casecmp_to]. </description> </method> <method name="nocasecmp_to" qualifiers="const"> <return type="int" /> <param index="0" name="to" type="String" /> <description> - Performs a case-insensitive comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters will be converted to uppercase during the comparison. - [b]Behavior with different string lengths:[/b] Returns [code]1[/code] if the "base" string is longer than the [param to] string or [code]-1[/code] if the "base" string is shorter than the [param to] string. Keep in mind this length is determined by the number of Unicode codepoints, [i]not[/i] the actual visible characters. - [b]Behavior with empty strings:[/b] Returns [code]-1[/code] if the "base" string is empty, [code]1[/code] if the [param to] string is empty or [code]0[/code] if both strings are empty. - To get a boolean result from a string comparison, use the [code]==[/code] operator instead. See also [method casecmp_to] and [method naturalnocasecmp_to]. + Performs a [b]case-insensitive[/b] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters are converted to uppercase for the comparison. + With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code]. + To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method casecmp_to] and [method naturalnocasecmp_to]. </description> </method> <method name="num" qualifiers="static"> @@ -511,20 +575,22 @@ <param index="0" name="number" type="float" /> <param index="1" name="decimals" type="int" default="-1" /> <description> - Converts a [float] to a string representation of a decimal number. - The number of decimal places can be specified with [param decimals]. If [param decimals] is [code]-1[/code] (default), decimal places will be automatically adjusted so that the string representation has 14 significant digits (counting both digits to the left and the right of the decimal point). - Trailing zeros are not included in the string. The last digit will be rounded and not truncated. + Converts a [float] to a string representation of a decimal number, with the number of decimal places specified in [param decimals]. + If [param decimals] is [code]-1[/code] as by default, the string representation may only have up to 14 significant digits, with digits before the decimal point having priority over digits after. + Trailing zeros are not included in the string. The last digit is rounded, not truncated. [b]Example:[/b] [codeblock] - String.num(3.141593) # "3.141593" - String.num(3.141593, 3) # "3.142" - String.num(3.14159300) # "3.141593", no trailing zeros. - # Last digit will be rounded up here, which reduces total digit count since - # trailing zeros are removed: - String.num(42.129999, 5) # "42.13" - # If `decimals` is not specified, the total number of significant digits is 14: - String.num(-0.0000012345432123454321) # "-0.00000123454321" - String.num(-10000.0000012345432123454321) # "-10000.0000012345" + String.num(3.141593) # Returns "3.141593" + String.num(3.141593, 3) # Returns "3.142" + String.num(3.14159300) # Returns "3.141593" + + # Here, the last digit will be rounded up, + # which reduces the total digit count, since trailing zeros are removed: + String.num(42.129999, 5) # Returns "42.13" + + # If `decimals` is not specified, the maximum number of significant digits is 14: + String.num(-0.0000012345432123454321) # Returns "-0.00000123454321" + String.num(-10000.0000012345432123454321) # Returns "-10000.0000012345" [/codeblock] </description> </method> @@ -534,13 +600,31 @@ <param index="1" name="base" type="int" default="10" /> <param index="2" name="capitalize_hex" type="bool" default="false" /> <description> - Converts a signed [int] to a string representation of a number. + Converts the given [param number] to a string representation, with the given [param base]. + By default, [param base] is set to decimal ([code]10[/code]). Other common bases in programming include binary ([code]2[/code]), [url=https://en.wikipedia.org/wiki/Octal]octal[/url] ([code]8[/code]), hexadecimal ([code]16[/code]). + If [param capitalize_hex] is [code]true[/code], digits higher than 9 are represented in uppercase. </description> </method> <method name="num_scientific" qualifiers="static"> <return type="String" /> <param index="0" name="number" type="float" /> <description> + Converts the given [param number] to a string representation, in scientific notation. + [codeblocks] + [gdscript] + var n = -5.2e8 + print(n) # Prints -520000000 + print(String.NumScientific(n)) # Prints -5.2e+08 + [/gdscript] + [csharp] + // This method is not implemented in C#. + // Use `string.ToString()` with "e" to achieve similar results. + var n = -5.2e8f; + GD.Print(n); // Prints -520000000 + GD.Print(n.ToString("e1")); // Prints -5.2e+008 + [/csharp] + [/codeblocks] + [b]Note:[/b] In C#, this method is not implemented. To achieve similar results, see C#'s [url=https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings]Standard numeric format strings[/url] </description> </method> <method name="num_uint64" qualifiers="static"> @@ -549,35 +633,38 @@ <param index="1" name="base" type="int" default="10" /> <param index="2" name="capitalize_hex" type="bool" default="false" /> <description> - Converts a unsigned [int] to a string representation of a number. + Converts the given unsigned [int] to a string representation, with the given [param base]. + By default, [param base] is set to decimal ([code]10[/code]). Other common bases in programming include binary ([code]2[/code]), [url=https://en.wikipedia.org/wiki/Octal]octal[/url] ([code]8[/code]), hexadecimal ([code]16[/code]). + If [param capitalize_hex] is [code]true[/code], digits higher than 9 are represented in uppercase. </description> </method> <method name="pad_decimals" qualifiers="const"> <return type="String" /> <param index="0" name="digits" type="int" /> <description> - Formats a number to have an exact number of [param digits] after the decimal point. + Formats the string representing a number to have an exact number of [param digits] [i]after[/i] the decimal point. </description> </method> <method name="pad_zeros" qualifiers="const"> <return type="String" /> <param index="0" name="digits" type="int" /> <description> - Formats a number to have an exact number of [param digits] before the decimal point. + Formats the string representing a number to have an exact number of [param digits] [i]before[/i] the decimal point. </description> </method> <method name="path_join" qualifiers="const"> <return type="String" /> <param index="0" name="file" type="String" /> <description> - If the string is a path, this concatenates [param file] at the end of the string as a subpath. E.g. [code]"this/is".path_join("path") == "this/is/path"[/code]. + Concatenates [param file] at the end of the string as a subpath, adding [code]/[/code] if necessary. + [b]Example:[/b] [code]"this/is".path_join("path") == "this/is/path"[/code]. </description> </method> <method name="repeat" qualifiers="const"> <return type="String" /> <param index="0" name="count" type="int" /> <description> - Returns original string repeated a number of times. The number of repetitions is given by the argument. + Repeats this string a number of times. [param count] needs to be greater than [code]0[/code]. Otherwise, returns an empty string. </description> </method> <method name="replace" qualifiers="const"> @@ -585,7 +672,7 @@ <param index="0" name="what" type="String" /> <param index="1" name="forwhat" type="String" /> <description> - Replaces occurrences of a case-sensitive substring with the given one inside the string. + Replaces all occurrences of [param what] inside the string with the given [param forwhat]. </description> </method> <method name="replacen" qualifiers="const"> @@ -593,7 +680,7 @@ <param index="0" name="what" type="String" /> <param index="1" name="forwhat" type="String" /> <description> - Replaces occurrences of a case-insensitive substring with the given one inside the string. + Replaces all [b]case-insensitive[/b] occurrences of [param what] inside the string with the given [param forwhat]. </description> </method> <method name="rfind" qualifiers="const"> @@ -601,7 +688,7 @@ <param index="0" name="what" type="String" /> <param index="1" name="from" type="int" default="-1" /> <description> - Returns the index of the [b]last[/b] case-sensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the beginning of the string. + Returns the index of the [b]last[/b] occurrence of [param what] in this string, or [code]-1[/code] if there are none. The search's start can be specified with [param from], continuing to the beginning of the string. This method is the reverse of [method find]. </description> </method> <method name="rfindn" qualifiers="const"> @@ -609,18 +696,17 @@ <param index="0" name="what" type="String" /> <param index="1" name="from" type="int" default="-1" /> <description> - Returns the index of the [b]last[/b] case-insensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the beginning of the string. + Returns the index of the [b]last[/b] [b]case-insensitive[/b] occurrence of [param what] in this string, or [code]-1[/code] if there are none. The starting search index can be specified with [param from], continuing to the beginning of the string. This method is the reverse of [method findn]. </description> </method> <method name="right" qualifiers="const"> <return type="String" /> <param index="0" name="length" type="int" /> <description> - Returns a number of characters from the right of the string. If negative [param length] is used, the characters are counted downwards from [String]'s length. - [b]Example:[/b] + Returns the last [param length] characters from the end of the string. If [param length] is negative, strips the first [param length] characters from the string's beginning. [codeblock] - print("sample text".right(3)) #prints "ext" - print("sample text".right(-3)) #prints "ple text" + print("Hello World!".right(3)) # Prints "ld!" + print("Hello World!".right(-4)) # Prints "o World!" [/codeblock] </description> </method> @@ -629,7 +715,7 @@ <param index="0" name="min_length" type="int" /> <param index="1" name="character" type="String" default="" "" /> <description> - Formats a string to be at least [param min_length] long by adding [param character]s to the right of the string. + Formats the string to be at least [param min_length] long, by adding [param character]s to the right of the string, if necessary. See also [method lpad]. </description> </method> <method name="rsplit" qualifiers="const"> @@ -638,21 +724,21 @@ <param index="1" name="allow_empty" type="bool" default="true" /> <param index="2" name="maxsplit" type="int" default="0" /> <description> - Splits the string by a [param delimiter] string and returns an array of the substrings, starting from right. If [param delimiter] is an empty string, each substring will be a single character. - The splits in the returned array are sorted in the same order as the original string, from left to right. - If [param allow_empty] is [code]true[/code], and there are two adjacent delimiters in the string, it will add an empty string to the array of substrings at this position. - If [param maxsplit] is specified, it defines the number of splits to do from the right up to [param maxsplit]. The default value of 0 means that all items are split, thus giving the same result as [method split]. + Splits the string using a [param delimiter] and returns an array of the substrings, starting from the end of the string. The splits in the returned array appear in the same order as the original string. If [param delimiter] is an empty string, each substring will be a single character. + If [param allow_empty] is [code]false[/code], empty strings between adjacent delimiters are excluded from the array. + If [param maxsplit] is greater than [code]0[/code], the number of splits may not exceed [param maxsplit]. By default, the entire string is split, which is mostly identical to [method split]. [b]Example:[/b] [codeblocks] [gdscript] var some_string = "One,Two,Three,Four" var some_array = some_string.rsplit(",", true, 1) + print(some_array.size()) # Prints 2 - print(some_array[0]) # Prints "One,Two,Three" - print(some_array[1]) # Prints "Four" + print(some_array[0]) # Prints "One,Two,Three" + print(some_array[1]) # Prints "Four" [/gdscript] [csharp] - // There is no Rsplit. + // In C#, there is no String.RSplit() method. [/csharp] [/codeblocks] </description> @@ -661,51 +747,55 @@ <return type="String" /> <param index="0" name="chars" type="String" /> <description> - Returns a copy of the string with characters removed from the right. The [param chars] argument is a string specifying the set of characters to be removed. - [b]Note:[/b] The [param chars] is not a suffix. See [method trim_suffix] method that will remove a single suffix string rather than a set of characters. + Removes a set of characters defined in [param chars] from the string's end. See also [method lstrip]. + [b]Note:[/b] [param chars] is not a suffix. Use [method trim_suffix] to remove a single suffix, rather than a set of characters. </description> </method> <method name="sha1_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Returns the SHA-1 hash of the string as an array of bytes. + Returns the [url=https://en.wikipedia.org/wiki/SHA-1]SHA-1[/url] hash of the string as a [PackedByteArray]. </description> </method> <method name="sha1_text" qualifiers="const"> <return type="String" /> <description> - Returns the SHA-1 hash of the string as a string. + Returns the [url=https://en.wikipedia.org/wiki/SHA-1]SHA-1[/url] hash of the string as another [String]. </description> </method> <method name="sha256_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Returns the SHA-256 hash of the string as an array of bytes. + Returns the [url=https://en.wikipedia.org/wiki/SHA-2]SHA-256[/url] hash of the string as a [PackedByteArray]. </description> </method> <method name="sha256_text" qualifiers="const"> <return type="String" /> <description> - Returns the SHA-256 hash of the string as a string. + Returns the [url=https://en.wikipedia.org/wiki/SHA-2]SHA-256[/url] hash of the string as another [String]. </description> </method> <method name="similarity" qualifiers="const"> <return type="float" /> <param index="0" name="text" type="String" /> <description> - Returns the similarity index ([url=https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient]Sorensen-Dice coefficient[/url]) of this string compared to another. A result of 1.0 means totally similar, while 0.0 means totally dissimilar. + Returns the similarity index ([url=https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient]Sorensen-Dice coefficient[/url]) of this string compared to another. A result of [code]1.0[/code] means totally similar, while [code]0.0[/code] means totally dissimilar. [codeblock] - print("ABC123".similarity("ABC123")) # Prints "1" - print("ABC123".similarity("XYZ456")) # Prints "0" - print("ABC123".similarity("123ABC")) # Prints "0.8" - print("ABC123".similarity("abc123")) # Prints "0.4" + print("ABC123".similarity("ABC123")) # Prints 1.0 + print("ABC123".similarity("XYZ456")) # Prints 0.0 + print("ABC123".similarity("123ABC")) # Prints 0.8 + print("ABC123".similarity("abc123")) # Prints 0.4 [/codeblock] </description> </method> <method name="simplify_path" qualifiers="const"> <return type="String" /> <description> - Returns a simplified canonical path. + If the string is a valid file path, converts the string into a canonical path. This is the shortest possible path, without [code]"./"[/code], and all the unnecessary [code]".."[/code] and [code]"/"[/code]. + [codeblock] + var simple_path = "./path/to///../file".simplify_path() + print(simple_path) # Prints "path/file" + [/codeblock] </description> </method> <method name="split" qualifiers="const"> @@ -714,27 +804,29 @@ <param index="1" name="allow_empty" type="bool" default="true" /> <param index="2" name="maxsplit" type="int" default="0" /> <description> - Splits the string by a [param delimiter] string and returns an array of the substrings. The [param delimiter] can be of any length. If [param delimiter] is an empty string, each substring will be a single character. - If [param allow_empty] is [code]true[/code], and there are two adjacent delimiters in the string, it will add an empty string to the array of substrings at this position. - If [param maxsplit] is specified, it defines the number of splits to do from the left up to [param maxsplit]. The default value of [code]0[/code] means that all items are split. - If you need only one element from the array at a specific index, [method get_slice] is a more performant option. + Splits the string using a [param delimiter] and returns an array of the substrings. If [param delimiter] is an empty string, each substring will be a single character. This method is the opposite of [method join]. + If [param allow_empty] is [code]false[/code], empty strings between adjacent delimiters are excluded from the array. + If [param maxsplit] is greater than [code]0[/code], the number of splits may not exceed [param maxsplit]. By default, the entire string is split. [b]Example:[/b] [codeblocks] [gdscript] - var some_string = "One,Two,Three,Four" - var some_array = some_string.split(",", true, 1) - print(some_array.size()) # Prints 2 - print(some_array[0]) # Prints "Four" - print(some_array[1]) # Prints "Three,Two,One" + var some_array = "One,Two,Three,Four".split(",", true, 2) + + print(some_array.size()) # Prints 3 + print(some_array[0]) # Prints "One" + print(some_array[1]) # Prints "Two" + print(some_array[2]) # Prints "Three,Four" [/gdscript] [csharp] - var someString = "One,Two,Three,Four"; - var someArray = someString.Split(",", true); // This is as close as it gets to Godots API. - GD.Print(someArray[0]); // Prints "Four" - GD.Print(someArray[1]); // Prints "Three,Two,One" + // C#'s `Split()` does not support the `maxsplit` parameter. + var someArray = "One,Two,Three".Split(","); + + GD.Print(someArray[0]); // Prints "One" + GD.Print(someArray[1]); // Prints "Two" + GD.Print(someArray[2]); // Prints "Three" [/csharp] [/codeblocks] - If you need to split strings with more complex rules, use the [RegEx] class instead. + [b]Note:[/b] If you only need one substring from the array, consider using [method get_slice] which is faster. If you need to split strings with more complex rules, use the [RegEx] class instead. </description> </method> <method name="split_floats" qualifiers="const"> @@ -742,9 +834,13 @@ <param index="0" name="delimiter" type="String" /> <param index="1" name="allow_empty" type="bool" default="true" /> <description> - Splits the string in floats by using a delimiter string and returns an array of the substrings. - For example, [code]"1,2.5,3"[/code] will return [code][1,2.5,3][/code] if split by [code]","[/code]. - If [param allow_empty] is [code]true[/code], and there are two adjacent delimiters in the string, it will add an empty string to the array of substrings at this position. + Splits the string into floats by using a [param delimiter] and returns a [PackedFloat64Array]. + If [param allow_empty] is [code]false[/code], empty or invalid [float] conversions between adjacent delimiters are excluded. + [codeblock] + var a = "1,2,4.5".split_floats(",") # a is [1.0, 2.0, 4.5] + var c = "1| ||4.5".split_floats("|") # c is [1.0, 0.0, 0.0, 4.5] + var b = "1| ||4.5".split_floats("|", false) # b is [1.0, 4.5] + [/codeblock] </description> </method> <method name="strip_edges" qualifiers="const"> @@ -752,13 +848,14 @@ <param index="0" name="left" type="bool" default="true" /> <param index="1" name="right" type="bool" default="true" /> <description> - Returns a copy of the string stripped of any non-printable character (including tabulations, spaces and line breaks) at the beginning and the end. The optional arguments are used to toggle stripping on the left and right edges respectively. + Strips all non-printable characters from the beginning and the end of the string. These include spaces, tabulations ([code]\t[/code]), and newlines ([code]\n[/code] [code]\r[/code]). + If [param left] is [code]false[/code], ignores the string's beginning. Likewise, if [param right] is [code]false[/code], ignores the string's end. </description> </method> <method name="strip_escapes" qualifiers="const"> <return type="String" /> <description> - Returns a copy of the string stripped of any escape character. These include all non-printable control characters of the first page of the ASCII table (< 32), such as tabulation ([code]\t[/code] in C) and newline ([code]\n[/code] and [code]\r[/code]) characters, but not spaces. + Strips all escape characters from the string. These include all non-printable control characters of the first page of the ASCII table (values from 0 to 31), such as tabulation ([code]\t[/code]) and newline ([code]\n[/code], [code]\r[/code]) characters, but [i]not[/i] spaces. </description> </method> <method name="substr" qualifiers="const"> @@ -766,13 +863,13 @@ <param index="0" name="from" type="int" /> <param index="1" name="len" type="int" default="-1" /> <description> - Returns part of the string from the position [param from] with length [param len]. Argument [param len] is optional and using [code]-1[/code] will return remaining characters from given position. + Returns part of the string from the position [param from] with length [param len]. If [param len] is [code]-1[/code] (as by default), returns the rest of the string starting from the given position. </description> </method> <method name="to_ascii_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Converts the String (which is a character array) to ASCII/Latin-1 encoded [PackedByteArray] (which is an array of bytes). The conversion is faster compared to [method to_utf8_buffer], as this method assumes that all the characters in the String are ASCII/Latin-1 characters, unsupported characters are replaced with spaces. + Converts the string to an [url=https://en.wikipedia.org/wiki/ASCII]ASCII[/url]/Latin-1 encoded [PackedByteArray]. This method is slightly faster than [method to_utf8_buffer], but replaces all unsupported characters with spaces. </description> </method> <method name="to_camel_case" qualifiers="const"> @@ -784,23 +881,25 @@ <method name="to_float" qualifiers="const"> <return type="float" /> <description> - Converts a string containing a decimal number into a [code]float[/code]. The method will stop on the first non-number character except the first [code].[/code] (decimal point), and [code]e[/code] which is used for exponential. + Converts the string representing a decimal number into a [float]. This method stops on the first non-number character, except the first decimal point ([code].[/code]) and the exponent letter ([code]e[/code]). See also [method is_valid_float]. [codeblock] - print("12.3".to_float()) # 12.3 - print("1.2.3".to_float()) # 1.2 - print("12ab3".to_float()) # 12 - print("1e3".to_float()) # 1000 + var a = "12.35".to_float() # a is 12.35 + var b = "1.2.3".to_float() # b is 1.2 + var c = "12xy3".to_float() # c is 12.0 + var d = "1e3".to_float() # d is 1000.0 + var e = "Hello!".to_int() # e is 0.0 [/codeblock] </description> </method> <method name="to_int" qualifiers="const"> <return type="int" /> <description> - Converts a string containing an integer number into an [code]int[/code]. The method will remove any non-number character and stop if it encounters a [code].[/code]. + Converts the string representing an integer number into an [int]. This method removes any non-number character and stops at the first decimal point ([code].[/code]). See also [method is_valid_int]. [codeblock] - print("123".to_int()) # 123 - print("a1b2c3".to_int()) # 123 - print("1.2.3".to_int()) # 1 + var a = "123".to_int() # a is 123 + var b = "x1y2z3".to_int() # b is 123 + var c = "-1.2.3".to_int() # c is -1 + var d = "Hello!".to_int() # d is 0 [/codeblock] </description> </method> @@ -831,33 +930,33 @@ <method name="to_utf16_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Converts the String (which is an array of characters) to UTF-16 encoded [PackedByteArray] (which is an array of bytes). + Converts the string to a [url=https://en.wikipedia.org/wiki/UTF-16]UTF-16[/url] encoded [PackedByteArray]. </description> </method> <method name="to_utf32_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Converts the String (which is an array of characters) to UTF-32 encoded [PackedByteArray] (which is an array of bytes). + Converts the string to a [url=https://en.wikipedia.org/wiki/UTF-32]UTF-32[/url] encoded [PackedByteArray]. </description> </method> <method name="to_utf8_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Converts the String (which is an array of characters) to UTF-8 encode [PackedByteArray] (which is an array of bytes). The conversion is a bit slower than [method to_ascii_buffer], but supports all UTF-8 characters. Therefore, you should prefer this function over [method to_ascii_buffer]. + Converts the string to a [url=https://en.wikipedia.org/wiki/UTF-8]UTF-8[/url] encoded [PackedByteArray]. This method is slightly slower than [method to_ascii_buffer], but supports all UTF-8 characters. For most cases, prefer using this method. </description> </method> <method name="trim_prefix" qualifiers="const"> <return type="String" /> <param index="0" name="prefix" type="String" /> <description> - Removes a given string from the start if it starts with it or leaves the string unchanged. + Removes the given [param prefix] from the start of the string, or returns the string unchanged. </description> </method> <method name="trim_suffix" qualifiers="const"> <return type="String" /> <param index="0" name="suffix" type="String" /> <description> - Removes a given string from the end if it ends with it or leaves the string unchanged. + Removes the given [param suffix] from the end of the string, or returns the string unchanged. </description> </method> <method name="unicode_at" qualifiers="const"> @@ -870,13 +969,15 @@ <method name="uri_decode" qualifiers="const"> <return type="String" /> <description> - Decodes a string in URL encoded format. This is meant to decode parameters in a URL when receiving an HTTP request. + Decodes the string from its URL-encoded format. This method is meant to properly decode the parameters in a URL when receiving an HTTP request. [codeblocks] [gdscript] - print("https://example.org/?escaped=" + "Godot%20Engine%3A%27docs%27".uri_decode()) + var url = "$DOCS_URL/?highlight=Godot%20Engine%3%docs" + print(url.uri_decode()) # Prints "$DOCS_URL/?hightlight=Godot Engine:docs" [/gdscript] [csharp] - GD.Print("https://example.org/?escaped=" + "Godot%20Engine%3a%27Docs%27".URIDecode()); + var url = "$DOCS_URL/?highlight=Godot%20Engine%3%docs" + GD.Print(url.URIDecode()) // Prints "$DOCS_URL/?hightlight=Godot Engine:docs" [/csharp] [/codeblocks] </description> @@ -884,13 +985,19 @@ <method name="uri_encode" qualifiers="const"> <return type="String" /> <description> - Encodes a string to URL friendly format. This is meant to encode parameters in a URL when sending an HTTP request. + Encodes the string to URL-friendly format. This method is meant to properly encode the parameters in a URL when sending an HTTP request. [codeblocks] [gdscript] - print("https://example.org/?escaped=" + "Godot Engine:'docs'".uri_encode()) + var prefix = "$DOCS_URL/?hightlight=" + var url = prefix + "Godot Engine:docs".uri_encode() + + print(url) # Prints "$DOCS_URL/?highlight=Godot%20Engine%3%docs" [/gdscript] [csharp] - GD.Print("https://example.org/?escaped=" + "Godot Engine:'docs'".URIEncode()); + var prefix = "$DOCS_URL/?hightlight="; + var url = prefix + "Godot Engine:docs".URIEncode(); + + GD.Print(url); // Prints "$DOCS_URL/?highlight=Godot%20Engine%3%docs" [/csharp] [/codeblocks] </description> @@ -898,7 +1005,7 @@ <method name="validate_node_name" qualifiers="const"> <return type="String" /> <description> - Removes any characters from the string that are prohibited in [Node] names ([code].[/code] [code]:[/code] [code]@[/code] [code]/[/code] [code]"[/code]). + Removes all characters that are not allowed in [member Node.name] from the string ([code].[/code] [code]:[/code] [code]@[/code] [code]/[/code] [code]"[/code] [code]%[/code]). </description> </method> <method name="xml_escape" qualifiers="const"> diff --git a/doc/classes/SystemFont.xml b/doc/classes/SystemFont.xml index b47d6ded7d..20bfd0d8ae 100644 --- a/doc/classes/SystemFont.xml +++ b/doc/classes/SystemFont.xml @@ -7,23 +7,32 @@ <description> [SystemFont] loads a font from a system font with the first matching name from [member font_names]. It will attempt to match font style, but it's not guaranteed. - The returned font might be part of a font collection or be a variable font with OpenType "weight" and/or "italic" features set. + The returned font might be part of a font collection or be a variable font with OpenType "weight", "width" and/or "italic" features set. You can create [FontVariation] of the system font for fine control over its features. </description> <tutorials> </tutorials> <members> + <member name="allow_system_fallback" type="bool" setter="set_allow_system_fallback" getter="is_allow_system_fallback" default="true"> + If set to [code]true[/code], system fonts can be automatically used as fallbacks. + </member> <member name="antialiasing" type="int" setter="set_antialiasing" getter="get_antialiasing" enum="TextServer.FontAntialiasing" default="1"> Font anti-aliasing mode. </member> <member name="fallbacks" type="Font[]" setter="set_fallbacks" getter="get_fallbacks" default="[]"> Array of fallback [Font]s. </member> + <member name="font_italic" type="bool" setter="set_font_italic" getter="get_font_italic" default="false"> + If set to [code]true[/code], italic or oblique font is preferred. + </member> <member name="font_names" type="PackedStringArray" setter="set_font_names" getter="get_font_names" default="PackedStringArray()"> Array of font family names to search, first matching font found is used. </member> - <member name="font_style" type="int" setter="set_font_style" getter="get_font_style" enum="TextServer.FontStyle" default="0"> - Font style flags, see [enum TextServer.FontStyle]. + <member name="font_stretch" type="int" setter="set_font_stretch" getter="get_font_stretch" default="100"> + Preferred font stretch amount, compared to a normal width. A percentage value between [code]50%[/code] and [code]200%[/code]. + </member> + <member name="font_weight" type="int" setter="set_font_weight" getter="get_font_weight" default="400"> + Preferred weight (boldness) of the font. A value in the [code]100...999[/code] range, normal font weight is [code]400[/code], bold font weight is [code]700[/code]. </member> <member name="force_autohinter" type="bool" setter="set_force_autohinter" getter="is_force_autohinter" default="false"> If set to [code]true[/code], auto-hinting is supported and preferred over font built-in hinting. diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml index f4d92ab9b6..b3e55b5cd0 100644 --- a/doc/classes/TextServer.xml +++ b/doc/classes/TextServer.xml @@ -362,6 +362,13 @@ Returns list of the font sizes in the cache. Each size is [code]Vector2i[/code] with font size and outline size. </description> </method> + <method name="font_get_stretch" qualifiers="const"> + <return type="int" /> + <param index="0" name="font_rid" type="RID" /> + <description> + Returns font stretch amount, compared to a normal width. A percentage value between [code]50%[/code] and [code]200%[/code]. + </description> + </method> <method name="font_get_style" qualifiers="const"> <return type="int" enum="TextServer.FontStyle" /> <param index="0" name="font_rid" type="RID" /> @@ -446,6 +453,13 @@ Returns variation coordinates for the specified font cache entry. See [method font_supported_variation_list] for more info. </description> </method> + <method name="font_get_weight" qualifiers="const"> + <return type="int" /> + <param index="0" name="font_rid" type="RID" /> + <description> + Returns weight (boldness) of the font. A value in the [code]100...999[/code] range, normal font weight is [code]400[/code], bold font weight is [code]700[/code]. + </description> + </method> <method name="font_has_char" qualifiers="const"> <return type="bool" /> <param index="0" name="font_rid" type="RID" /> @@ -454,6 +468,13 @@ Returns [code]true[/code] if a Unicode [param char] is available in the font. </description> </method> + <method name="font_is_allow_system_fallback" qualifiers="const"> + <return type="bool" /> + <param index="0" name="font_rid" type="RID" /> + <description> + Returns [code]true[/code] if system fonts can be automatically used as fallbacks. + </description> + </method> <method name="font_is_force_autohinter" qualifiers="const"> <return type="bool" /> <param index="0" name="font_rid" type="RID" /> @@ -556,6 +577,14 @@ Renders the range of characters to the font cache texture. </description> </method> + <method name="font_set_allow_system_fallback"> + <return type="void" /> + <param index="0" name="font_rid" type="RID" /> + <param index="1" name="allow_system_fallback" type="bool" /> + <description> + If set to [code]true[/code], system fonts can be automatically used as fallbacks. + </description> + </method> <method name="font_set_antialiasing"> <return type="void" /> <param index="0" name="font_rid" type="RID" /> @@ -783,12 +812,22 @@ Adds override for [method font_is_script_supported]. </description> </method> + <method name="font_set_stretch"> + <return type="void" /> + <param index="0" name="font_rid" type="RID" /> + <param index="1" name="weight" type="int" /> + <description> + Sets font stretch amount, compared to a normal width. A percentage value between [code]50%[/code] and [code]200%[/code]. + [b]Note:[/b] This value is used for font matching only and will not affect font rendering. Use [method font_set_face_index], [method font_set_variation_coordinates], or [method font_set_transform] instead. + </description> + </method> <method name="font_set_style"> <return type="void" /> <param index="0" name="font_rid" type="RID" /> <param index="1" name="style" type="int" enum="TextServer.FontStyle" /> <description> Sets the font style flags, see [enum FontStyle]. + [b]Note:[/b] This value is used for font matching only and will not affect font rendering. Use [method font_set_face_index], [method font_set_variation_coordinates], [method font_set_embolden], or [method font_set_transform] instead. </description> </method> <method name="font_set_style_name"> @@ -862,6 +901,15 @@ Sets variation coordinates for the specified font cache entry. See [method font_supported_variation_list] for more info. </description> </method> + <method name="font_set_weight"> + <return type="void" /> + <param index="0" name="font_rid" type="RID" /> + <param index="1" name="weight" type="int" /> + <description> + Sets weight (boldness) of the font. A value in the [code]100...999[/code] range, normal font weight is [code]400[/code], bold font weight is [code]700[/code]. + [b]Note:[/b] This value is used for font matching only and will not affect font rendering. Use [method font_set_face_index], [method font_set_variation_coordinates], or [method font_set_embolden] instead. + </description> + </method> <method name="font_supported_feature_list" qualifiers="const"> <return type="Dictionary" /> <param index="0" name="font_rid" type="RID" /> diff --git a/doc/classes/TextServerExtension.xml b/doc/classes/TextServerExtension.xml index 37d2698dd4..4c9817fcd4 100644 --- a/doc/classes/TextServerExtension.xml +++ b/doc/classes/TextServerExtension.xml @@ -9,6 +9,11 @@ <tutorials> </tutorials> <methods> + <method name="_cleanup" qualifiers="virtual"> + <return type="void" /> + <description> + </description> + </method> <method name="_create_font" qualifiers="virtual"> <return type="RID" /> <description> @@ -306,6 +311,12 @@ <description> </description> </method> + <method name="_font_get_stretch" qualifiers="virtual const"> + <return type="int" /> + <param index="0" name="font_rid" type="RID" /> + <description> + </description> + </method> <method name="_font_get_style" qualifiers="virtual const"> <return type="int" enum="TextServer.FontStyle" /> <param index="0" name="font_rid" type="RID" /> @@ -379,6 +390,12 @@ <description> </description> </method> + <method name="_font_get_weight" qualifiers="virtual const"> + <return type="int" /> + <param index="0" name="font_rid" type="RID" /> + <description> + </description> + </method> <method name="_font_has_char" qualifiers="virtual const"> <return type="bool" /> <param index="0" name="font_rid" type="RID" /> @@ -386,6 +403,12 @@ <description> </description> </method> + <method name="_font_is_allow_system_fallback" qualifiers="virtual const"> + <return type="bool" /> + <param index="0" name="font_rid" type="RID" /> + <description> + </description> + </method> <method name="_font_is_force_autohinter" qualifiers="virtual const"> <return type="bool" /> <param index="0" name="font_rid" type="RID" /> @@ -474,6 +497,13 @@ <description> </description> </method> + <method name="_font_set_allow_system_fallback" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="font_rid" type="RID" /> + <param index="1" name="allow_system_fallback" type="bool" /> + <description> + </description> + </method> <method name="_font_set_antialiasing" qualifiers="virtual"> <return type="void" /> <param index="0" name="font_rid" type="RID" /> @@ -680,6 +710,13 @@ <description> </description> </method> + <method name="_font_set_stretch" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="font_rid" type="RID" /> + <param index="1" name="stretch" type="int" /> + <description> + </description> + </method> <method name="_font_set_style" qualifiers="virtual"> <return type="void" /> <param index="0" name="font_rid" type="RID" /> @@ -749,6 +786,13 @@ <description> </description> </method> + <method name="_font_set_weight" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="font_rid" type="RID" /> + <param index="1" name="weight" type="int" /> + <description> + </description> + </method> <method name="_font_supported_feature_list" qualifiers="virtual const"> <return type="Dictionary" /> <param index="0" name="font_rid" type="RID" /> diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 01246f0c2e..e9e2a738d3 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -174,7 +174,7 @@ Creates a new [TileMapPattern] from the given layer and set of cells. </description> </method> - <method name="get_surrounding_tiles"> + <method name="get_surrounding_cells"> <return type="Vector2i[]" /> <param index="0" name="coords" type="Vector2i" /> <description> diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml index 4156030d77..1cd73688ee 100644 --- a/doc/classes/Vector2.xml +++ b/doc/classes/Vector2.xml @@ -93,7 +93,7 @@ <param index="2" name="end" type="Vector2" /> <param index="3" name="t" type="float" /> <description> - Returns the derivative at the given [param t] on the [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bezier curve[/url] defined by this vector and the given [param control_1], [param control_2], and [param end] points. + Returns the derivative at the given [param t] on the [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bézier curve[/url] defined by this vector and the given [param control_1], [param control_2], and [param end] points. </description> </method> <method name="bezier_interpolate" qualifiers="const"> @@ -103,7 +103,7 @@ <param index="2" name="end" type="Vector2" /> <param index="3" name="t" type="float" /> <description> - Returns the point at the given [param t] on the [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bezier curve[/url] defined by this vector and the given [param control_1], [param control_2], and [param end] points. + Returns the point at the given [param t] on the [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bézier curve[/url] defined by this vector and the given [param control_1], [param control_2], and [param end] points. </description> </method> <method name="bounce" qualifiers="const"> diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml index 2896408505..1d7eda6bb9 100644 --- a/doc/classes/Vector3.xml +++ b/doc/classes/Vector3.xml @@ -69,7 +69,7 @@ <param index="2" name="end" type="Vector3" /> <param index="3" name="t" type="float" /> <description> - Returns the derivative at the given [param t] on the [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bezier curve[/url] defined by this vector and the given [param control_1], [param control_2], and [param end] points. + Returns the derivative at the given [param t] on the [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bézier curve[/url] defined by this vector and the given [param control_1], [param control_2], and [param end] points. </description> </method> <method name="bezier_interpolate" qualifiers="const"> @@ -79,7 +79,7 @@ <param index="2" name="end" type="Vector3" /> <param index="3" name="t" type="float" /> <description> - Returns the point at the given [param t] on the [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bezier curve[/url] defined by this vector and the given [param control_1], [param control_2], and [param end] points. + Returns the point at the given [param t] on the [url=https://en.wikipedia.org/wiki/B%C3%A9zier_curve]Bézier curve[/url] defined by this vector and the given [param control_1], [param control_2], and [param end] points. </description> </method> <method name="bounce" qualifiers="const"> diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml index 8707ef003a..d736f103ab 100644 --- a/doc/classes/Window.xml +++ b/doc/classes/Window.xml @@ -41,7 +41,13 @@ Returns layout direction and text writing direction. </description> </method> - <method name="get_real_size" qualifiers="const"> + <method name="get_position_with_decorations" qualifiers="const"> + <return type="Vector2i" /> + <description> + Returns the window's position including its border. + </description> + </method> + <method name="get_size_with_decorations" qualifiers="const"> <return type="Vector2i" /> <description> Returns the window's size including its border. diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index e5a0bbb008..1f71b77cbd 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -240,7 +240,7 @@ class State: enum_def = class_def.enums[enum] else: - enum_def = EnumDef(enum, is_bitfield) + enum_def = EnumDef(enum, TypeName("int", enum), is_bitfield) class_def.enums[enum] = enum_def enum_def.values[constant_name] = constant_def @@ -458,9 +458,10 @@ class ConstantDef(DefinitionBase): class EnumDef(DefinitionBase): - def __init__(self, name: str, bitfield: bool) -> None: + def __init__(self, name: str, type_name: TypeName, bitfield: bool) -> None: super().__init__("enum", name) + self.type_name = type_name self.values: OrderedDict[str, ConstantDef] = OrderedDict() self.is_bitfield = bitfield @@ -754,7 +755,8 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: f.write(f".. _class_{class_name}:\n\n") f.write(make_heading(class_name, "=", False)) - # Inheritance tree + ### INHERITANCE TREE ### + # Ascendants if class_def.inherits: inherits = class_def.inherits.strip() @@ -788,6 +790,8 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: f.write(make_type(child, state)) f.write("\n\n") + ### INTRODUCTION ### + has_description = False # Brief description @@ -800,7 +804,9 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: if class_def.description is not None and class_def.description.strip() != "": has_description = True + f.write(".. rst-class:: classref-introduction-group\n\n") f.write(make_heading("Description", "-")) + f.write(f"{format_text_block(class_def.description.strip(), class_def, state)}\n\n") if not has_description: @@ -814,14 +820,22 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: # Online tutorials if len(class_def.tutorials) > 0: + f.write(".. rst-class:: classref-introduction-group\n\n") f.write(make_heading("Tutorials", "-")) + for url, title in class_def.tutorials: f.write(f"- {make_link(url, title)}\n\n") - # Properties overview + ### REFERENCE TABLES ### + + # Reused container for reference tables. ml: List[Tuple[Optional[str], ...]] = [] + + # Properties reference table if len(class_def.properties) > 0: + f.write(".. rst-class:: classref-reftable-group\n\n") f.write(make_heading("Properties", "-")) + ml = [] for property_def in class_def.properties.values(): type_rst = property_def.type_name.to_rst(state) @@ -833,76 +847,108 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: else: ref = f":ref:`{property_def.name}<class_{class_name}_property_{property_def.name}>`" ml.append((type_rst, ref, default)) + format_table(f, ml, True) - # Constructors, Methods, Operators overview + # Constructors, Methods, Operators reference tables if len(class_def.constructors) > 0: + f.write(".. rst-class:: classref-reftable-group\n\n") f.write(make_heading("Constructors", "-")) + ml = [] for method_list in class_def.constructors.values(): for m in method_list: ml.append(make_method_signature(class_def, m, "constructor", state)) + format_table(f, ml) if len(class_def.methods) > 0: + f.write(".. rst-class:: classref-reftable-group\n\n") f.write(make_heading("Methods", "-")) + ml = [] for method_list in class_def.methods.values(): for m in method_list: ml.append(make_method_signature(class_def, m, "method", state)) + format_table(f, ml) if len(class_def.operators) > 0: + f.write(".. rst-class:: classref-reftable-group\n\n") f.write(make_heading("Operators", "-")) + ml = [] for method_list in class_def.operators.values(): for m in method_list: ml.append(make_method_signature(class_def, m, "operator", state)) + format_table(f, ml) - # Theme properties + # Theme properties reference table if len(class_def.theme_items) > 0: + f.write(".. rst-class:: classref-reftable-group\n\n") f.write(make_heading("Theme Properties", "-")) - pl: List[Tuple[Optional[str], ...]] = [] + + ml = [] for theme_item_def in class_def.theme_items.values(): ref = f":ref:`{theme_item_def.name}<class_{class_name}_theme_{theme_item_def.data_name}_{theme_item_def.name}>`" - pl.append((theme_item_def.type_name.to_rst(state), ref, theme_item_def.default_value)) - format_table(f, pl, True) + ml.append((theme_item_def.type_name.to_rst(state), ref, theme_item_def.default_value)) + + format_table(f, ml, True) - # Signals + ### DETAILED DESCRIPTIONS ### + + # Signal descriptions if len(class_def.signals) > 0: + f.write(make_separator(True)) + f.write(".. rst-class:: classref-descriptions-group\n\n") f.write(make_heading("Signals", "-")) + index = 0 for signal in class_def.signals.values(): if index != 0: - f.write("----\n\n") + f.write(make_separator()) + + # Create signal signature and anchor point. f.write(f".. _class_{class_name}_signal_{signal.name}:\n\n") + f.write(".. rst-class:: classref-signal\n\n") + _, signature = make_method_signature(class_def, signal, "", state) - f.write(f"- {signature}\n\n") + f.write(f"{signature}\n\n") + + # Add signal description, or a call to action if it's missing. if signal.description is not None and signal.description.strip() != "": f.write(f"{format_text_block(signal.description.strip(), signal, state)}\n\n") + else: + f.write(".. container:: contribute\n\n\t") + f.write( + translate( + "There is currently no description for this signal. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!" + ) + + "\n\n" + ) index += 1 - # Enums + # Enumeration descriptions if len(class_def.enums) > 0: + f.write(make_separator(True)) + f.write(".. rst-class:: classref-descriptions-group\n\n") f.write(make_heading("Enumerations", "-")) + index = 0 for e in class_def.enums.values(): if index != 0: - f.write("----\n\n") + f.write(make_separator()) + + # Create enumeration signature and anchor point. f.write(f".. _enum_{class_name}_{e.name}:\n\n") - # Sphinx seems to divide the bullet list into individual <ul> tags if we weave the labels into it. - # As such I'll put them all above the list. Won't be perfect but better than making the list visually broken. - # As to why I'm not modifying the reference parser to directly link to the _enum label: - # If somebody gets annoyed enough to fix it, all existing references will magically improve. - for value in e.values.values(): - f.write(f".. _class_{class_name}_constant_{value.name}:\n\n") + f.write(".. rst-class:: classref-enumeration\n\n") if e.is_bitfield: f.write(f"flags **{e.name}**:\n\n") @@ -910,45 +956,66 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: f.write(f"enum **{e.name}**:\n\n") for value in e.values.values(): - f.write(f"- **{value.name}** = **{value.value}**") + # Also create signature and anchor point for each enum constant. + + f.write(f".. _class_{class_name}_constant_{value.name}:\n\n") + f.write(".. rst-class:: classref-enumeration-constant\n\n") + + f.write(f"{e.type_name.to_rst(state)} **{value.name}** = ``{value.value}``\n\n") + + # Add enum constant description. + if value.text is not None and value.text.strip() != "": - # If value.text contains a bullet point list, each entry needs additional indentation - f.write(f" --- {indent_bullets(format_text_block(value.text.strip(), value, state))}") + f.write(f"{format_text_block(value.text.strip(), value, state)}") f.write("\n\n") index += 1 - # Constants + # Constant descriptions if len(class_def.constants) > 0: + f.write(make_separator(True)) + f.write(".. rst-class:: classref-descriptions-group\n\n") f.write(make_heading("Constants", "-")) - # Sphinx seems to divide the bullet list into individual <ul> tags if we weave the labels into it. - # As such I'll put them all above the list. Won't be perfect but better than making the list visually broken. + for constant in class_def.constants.values(): + # Create constant signature and anchor point. + f.write(f".. _class_{class_name}_constant_{constant.name}:\n\n") + f.write(".. rst-class:: classref-constant\n\n") + + f.write(f"**{constant.name}** = ``{constant.value}``\n\n") + + # Add enum constant description. - for constant in class_def.constants.values(): - f.write(f"- **{constant.name}** = **{constant.value}**") if constant.text is not None and constant.text.strip() != "": - f.write(f" --- {format_text_block(constant.text.strip(), constant, state)}") + f.write(f"{format_text_block(constant.text.strip(), constant, state)}") f.write("\n\n") - # Annotations + # Annotation descriptions if len(class_def.annotations) > 0: + f.write(make_separator(True)) f.write(make_heading("Annotations", "-")) + index = 0 for method_list in class_def.annotations.values(): # type: ignore for i, m in enumerate(method_list): if index != 0: - f.write("----\n\n") + f.write(make_separator()) + + # Create annotation signature and anchor point. if i == 0: f.write(f".. _class_{class_name}_annotation_{m.name}:\n\n") + f.write(".. rst-class:: classref-annotation\n\n") + _, signature = make_method_signature(class_def, m, "", state) - f.write(f"- {signature}\n\n") + f.write(f"{signature}\n\n") + + # Add annotation description, or a call to action if it's missing. if m.description is not None and m.description.strip() != "": f.write(f"{format_text_block(m.description.strip(), m, state)}\n\n") @@ -965,7 +1032,10 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: # Property descriptions if any(not p.overrides for p in class_def.properties.values()) > 0: + f.write(make_separator(True)) + f.write(".. rst-class:: classref-descriptions-group\n\n") f.write(make_heading("Property Descriptions", "-")) + index = 0 for property_def in class_def.properties.values(): @@ -973,22 +1043,36 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: continue if index != 0: - f.write("----\n\n") + f.write(make_separator()) + + # Create property signature and anchor point. f.write(f".. _class_{class_name}_property_{property_def.name}:\n\n") - f.write(f"- {property_def.type_name.to_rst(state)} **{property_def.name}**\n\n") + f.write(".. rst-class:: classref-property\n\n") - info: List[Tuple[Optional[str], ...]] = [] - # Not using translate() for now as it breaks table formatting. + property_default = "" if property_def.default_value is not None: - info.append(("*Default*", property_def.default_value)) + property_default = f" = {property_def.default_value}" + f.write(f"{property_def.type_name.to_rst(state)} **{property_def.name}**{property_default}\n\n") + + # Create property setter and getter records. + + property_setget = "" + if property_def.setter is not None and not property_def.setter.startswith("_"): - info.append(("*Setter*", f"{property_def.setter}(value)")) + property_setter = make_setter_signature(class_def, property_def, state) + property_setget += f"- {property_setter}\n" + if property_def.getter is not None and not property_def.getter.startswith("_"): - info.append(("*Getter*", f"{property_def.getter}()")) + property_getter = make_getter_signature(class_def, property_def, state) + property_setget += f"- {property_getter}\n" + + if property_setget != "": + f.write(".. rst-class:: classref-property-setget\n\n") + f.write(property_setget) + f.write("\n") - if len(info) > 0: - format_table(f, info) + # Add property description, or a call to action if it's missing. if property_def.text is not None and property_def.text.strip() != "": f.write(f"{format_text_block(property_def.text.strip(), property_def, state)}\n\n") @@ -1005,19 +1089,28 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: # Constructor, Method, Operator descriptions if len(class_def.constructors) > 0: + f.write(make_separator(True)) + f.write(".. rst-class:: classref-descriptions-group\n\n") f.write(make_heading("Constructor Descriptions", "-")) + index = 0 for method_list in class_def.constructors.values(): for i, m in enumerate(method_list): if index != 0: - f.write("----\n\n") + f.write(make_separator()) + + # Create constructor signature and anchor point. if i == 0: f.write(f".. _class_{class_name}_constructor_{m.name}:\n\n") + f.write(".. rst-class:: classref-constructor\n\n") + ret_type, signature = make_method_signature(class_def, m, "", state) - f.write(f"- {ret_type} {signature}\n\n") + f.write(f"{ret_type} {signature}\n\n") + + # Add constructor description, or a call to action if it's missing. if m.description is not None and m.description.strip() != "": f.write(f"{format_text_block(m.description.strip(), m, state)}\n\n") @@ -1033,19 +1126,28 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: index += 1 if len(class_def.methods) > 0: + f.write(make_separator(True)) + f.write(".. rst-class:: classref-descriptions-group\n\n") f.write(make_heading("Method Descriptions", "-")) + index = 0 for method_list in class_def.methods.values(): for i, m in enumerate(method_list): if index != 0: - f.write("----\n\n") + f.write(make_separator()) + + # Create method signature and anchor point. if i == 0: f.write(f".. _class_{class_name}_method_{m.name}:\n\n") + f.write(".. rst-class:: classref-method\n\n") + ret_type, signature = make_method_signature(class_def, m, "", state) - f.write(f"- {ret_type} {signature}\n\n") + f.write(f"{ret_type} {signature}\n\n") + + # Add method description, or a call to action if it's missing. if m.description is not None and m.description.strip() != "": f.write(f"{format_text_block(m.description.strip(), m, state)}\n\n") @@ -1061,20 +1163,31 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: index += 1 if len(class_def.operators) > 0: + f.write(make_separator(True)) + f.write(".. rst-class:: classref-descriptions-group\n\n") f.write(make_heading("Operator Descriptions", "-")) + index = 0 for method_list in class_def.operators.values(): for i, m in enumerate(method_list): if index != 0: - f.write("----\n\n") - out = f".. _class_{class_name}_operator_{sanitize_operator_name(m.name, state)}" + f.write(make_separator()) + + # Create operator signature and anchor point. + + operator_anchor = f".. _class_{class_name}_operator_{sanitize_operator_name(m.name, state)}" for parameter in m.parameters: - out += f"_{parameter.type_name.type_name}" - out += f":\n\n" - f.write(out) + operator_anchor += f"_{parameter.type_name.type_name}" + operator_anchor += f":\n\n" + f.write(operator_anchor) + + f.write(".. rst-class:: classref-operator\n\n") + ret_type, signature = make_method_signature(class_def, m, "", state) - f.write(f"- {ret_type} {signature}\n\n") + f.write(f"{ret_type} {signature}\n\n") + + # Add operator description, or a call to action if it's missing. if m.description is not None and m.description.strip() != "": f.write(f"{format_text_block(m.description.strip(), m, state)}\n\n") @@ -1091,23 +1204,27 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: # Theme property descriptions if len(class_def.theme_items) > 0: + f.write(make_separator(True)) + f.write(".. rst-class:: classref-descriptions-group\n\n") f.write(make_heading("Theme Property Descriptions", "-")) + index = 0 for theme_item_def in class_def.theme_items.values(): if index != 0: - f.write("----\n\n") + f.write(make_separator()) + + # Create theme property signature and anchor point. f.write(f".. _class_{class_name}_theme_{theme_item_def.data_name}_{theme_item_def.name}:\n\n") - f.write(f"- {theme_item_def.type_name.to_rst(state)} **{theme_item_def.name}**\n\n") + f.write(".. rst-class:: classref-themeproperty\n\n") - info = [] + theme_item_default = "" if theme_item_def.default_value is not None: - # Not using translate() for now as it breaks table formatting. - info.append(("*Default*", theme_item_def.default_value)) + theme_item_default = f" = {theme_item_def.default_value}" + f.write(f"{theme_item_def.type_name.to_rst(state)} **{theme_item_def.name}**{theme_item_default}\n\n") - if len(info) > 0: - format_table(f, info) + # Add theme property description, or a call to action if it's missing. if theme_item_def.text is not None and theme_item_def.text.strip() != "": f.write(f"{format_text_block(theme_item_def.text.strip(), theme_item_def, state)}\n\n") @@ -1216,6 +1333,39 @@ def make_method_signature( return ret_type, out +def make_setter_signature(class_def: ClassDef, property_def: PropertyDef, state: State) -> str: + if property_def.setter is None: + return "" + + # If setter is a method available as a method definition, we use that. + if property_def.setter in class_def.methods: + setter = class_def.methods[property_def.setter][0] + # Otherwise we fake it with the information we have available. + else: + setter_params: List[ParameterDef] = [] + setter_params.append(ParameterDef("value", property_def.type_name, None)) + setter = MethodDef(property_def.setter, TypeName("void"), setter_params, None, None) + + ret_type, signature = make_method_signature(class_def, setter, "", state) + return f"{ret_type} {signature}" + + +def make_getter_signature(class_def: ClassDef, property_def: PropertyDef, state: State) -> str: + if property_def.getter is None: + return "" + + # If getter is a method available as a method definition, we use that. + if property_def.getter in class_def.methods: + getter = class_def.methods[property_def.getter][0] + # Otherwise we fake it with the information we have available. + else: + getter_params: List[ParameterDef] = [] + getter = MethodDef(property_def.getter, property_def.type_name, getter_params, None, None) + + ret_type, signature = make_method_signature(class_def, getter, "", state) + return f"{ret_type} {signature}" + + def make_heading(title: str, underline: str, l10n: bool = True) -> str: if l10n: new_title = translate(title) @@ -1247,6 +1397,14 @@ def make_footer() -> str: ) +def make_separator(section_level: bool = False) -> str: + separator_class = "item" + if section_level: + separator_class = "section" + + return f".. rst-class:: classref-{separator_class}-separator\n\n----\n\n" + + def make_link(url: str, title: str) -> str: match = GODOT_DOCS_PATTERN.search(url) if match: @@ -1407,10 +1565,10 @@ def format_text_block( escape_post = False # Tag is a reference to a class. - if tag_text in state.classes: + if tag_text in state.classes and not inside_code: if tag_text == state.current_class: - # Don't create a link to the same class, format it as inline code. - tag_text = f"``{tag_text}``" + # Don't create a link to the same class, format it as strong emphasis. + tag_text = f"**{tag_text}**" else: tag_text = make_type(tag_text, state) escape_pre = True @@ -1872,6 +2030,11 @@ def format_table(f: TextIO, data: List[Tuple[Optional[str], ...]], remove_empty_ if len(data) == 0: return + f.write(".. table::\n") + f.write(" :widths: auto\n\n") + + # Calculate the width of each column first, we will use this information + # to properly format RST-style tables. column_sizes = [0] * len(data[0]) for row in data: for i, text in enumerate(row): @@ -1879,14 +2042,21 @@ def format_table(f: TextIO, data: List[Tuple[Optional[str], ...]], remove_empty_ if text_length > column_sizes[i]: column_sizes[i] = text_length + # Each table row is wrapped in two separators, consecutive rows share the same separator. + # All separators, or rather borders, have the same shape and content. We compose it once, + # then reuse it. + sep = "" for size in column_sizes: if size == 0 and remove_empty_columns: continue - sep += "+" + "-" * (size + 2) + sep += "+" + "-" * (size + 2) # Content of each cell is padded by 1 on each side. sep += "+\n" - f.write(sep) + # Draw the first separator. + f.write(f" {sep}") + + # Draw each row and close it with a separator. for row in data: row_text = "|" for i, text in enumerate(row): @@ -1894,8 +2064,10 @@ def format_table(f: TextIO, data: List[Tuple[Optional[str], ...]], remove_empty_ continue row_text += f' {(text or "").ljust(column_sizes[i])} |' row_text += "\n" - f.write(row_text) - f.write(sep) + + f.write(f" {row_text}") + f.write(f" {sep}") + f.write("\n") @@ -1957,24 +2129,5 @@ def sanitize_operator_name(dirty_name: str, state: State) -> str: return clear_name -def indent_bullets(text: str) -> str: - # Take the text and check each line for a bullet point represented by "-". - # Where found, indent the given line by a further "\t". - # Used to properly indent bullet points contained in the description for enum values. - # Ignore the first line - text will be prepended to it so bullet points wouldn't work anyway. - bullet_points = "-" - - lines = text.splitlines(keepends=True) - for line_index, line in enumerate(lines[1:], start=1): - pos = 0 - while pos < len(line) and line[pos] == "\t": - pos += 1 - - if pos < len(line) and line[pos] in bullet_points: - lines[line_index] = f"{line[:pos]}\t{line[pos:]}" - - return "".join(lines) - - if __name__ == "__main__": main() diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index 1b42b55425..7b4131b3a3 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -285,6 +285,15 @@ void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, Display ERR_FAIL_COND(!rt); + // We normally render to the render target upside down, so flip Y when blitting to the screen. + bool flip_y = true; + if (rt->overridden.color.is_valid()) { + // If we've overridden the render target's color texture, that means we + // didn't render upside down, so we don't need to flip it. + // We're probably rendering directly to an XR device. + flip_y = false; + } + GLuint read_fbo = 0; if (rt->view_count > 1) { glGenFramebuffers(1, &read_fbo); @@ -296,10 +305,9 @@ void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, Display glReadBuffer(GL_COLOR_ATTACHMENT0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); - // Flip content upside down to correct for coordinates. Vector2i screen_rect_end = p_screen_rect.get_end(); glBlitFramebuffer(0, 0, rt->size.x, rt->size.y, - p_screen_rect.position.x, screen_rect_end.y, screen_rect_end.x, p_screen_rect.position.y, + p_screen_rect.position.x, flip_y ? screen_rect_end.y : p_screen_rect.position.y, screen_rect_end.x, flip_y ? p_screen_rect.position.y : screen_rect_end.y, GL_COLOR_BUFFER_BIT, GL_NEAREST); if (read_fbo != 0) { diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 8250140c3f..247b89658a 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -788,7 +788,7 @@ void RasterizerSceneGLES3::_draw_sky(RID p_env, const Projection &p_projection, } Basis sky_transform = environment_get_sky_orientation(p_env); sky_transform.invert(); - sky_transform = p_transform.basis * sky_transform; + sky_transform = sky_transform * p_transform.basis; bool success = material_storage->shaders.sky_shader.version_bind_shader(shader_data->version, SkyShaderGLES3::MODE_BACKGROUND); if (!success) { @@ -1321,6 +1321,7 @@ void RasterizerSceneGLES3::_setup_environment(const RenderDataGLES3 *p_render_da GLES3::MaterialStorage::store_camera(projection.inverse(), scene_state.ubo.inv_projection_matrix); GLES3::MaterialStorage::store_transform(p_render_data->cam_transform, scene_state.ubo.inv_view_matrix); GLES3::MaterialStorage::store_transform(p_render_data->inv_cam_transform, scene_state.ubo.view_matrix); + scene_state.ubo.camera_visible_layers = p_render_data->camera_visible_layers; if (p_render_data->view_count > 1) { for (uint32_t v = 0; v < p_render_data->view_count; v++) { @@ -1705,6 +1706,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ render_data.inv_cam_transform = render_data.cam_transform.affine_inverse(); render_data.cam_projection = p_camera_data->main_projection; render_data.cam_orthogonal = p_camera_data->is_orthogonal; + render_data.camera_visible_layers = p_camera_data->visible_layers; render_data.view_count = p_camera_data->view_count; for (uint32_t v = 0; v < p_camera_data->view_count; v++) { @@ -1900,7 +1902,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ glColorMask(0, 0, 0, 0); glClearDepth(1.0f); glClear(GL_DEPTH_BUFFER_BIT); - uint32_t spec_constant = SceneShaderGLES3::DISABLE_FOG | SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL | + uint64_t spec_constant = SceneShaderGLES3::DISABLE_FOG | SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL | SceneShaderGLES3::DISABLE_LIGHTMAP | SceneShaderGLES3::DISABLE_LIGHT_OMNI | SceneShaderGLES3::DISABLE_LIGHT_SPOT; @@ -1941,7 +1943,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ glClearBufferfv(GL_COLOR, 0, clear_color.components); } RENDER_TIMESTAMP("Render Opaque Pass"); - uint32_t spec_constant_base_flags = 0; + uint64_t spec_constant_base_flags = 0; { // Specialization Constants that apply for entire rendering pass. @@ -2012,8 +2014,10 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, GeometryInstanceGLES3 *prev_inst = nullptr; SceneShaderGLES3::ShaderVariant prev_variant = SceneShaderGLES3::ShaderVariant::MODE_COLOR; SceneShaderGLES3::ShaderVariant shader_variant = SceneShaderGLES3::MODE_COLOR; // Assigned to silence wrong -Wmaybe-initialized + uint64_t prev_spec_constants = 0; - uint32_t base_spec_constants = p_params->spec_constant_base_flags; + // Specializations constants used by all instances in the scene. + uint64_t base_spec_constants = p_params->spec_constant_base_flags; if (p_render_data->view_count > 1) { base_spec_constants |= SceneShaderGLES3::USE_MULTIVIEW; @@ -2233,8 +2237,18 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, instance_variant = SceneShaderGLES3::ShaderVariant(1 + int(shader_variant)); } - if (prev_shader != shader || prev_variant != instance_variant) { - bool success = material_storage->shaders.scene_shader.version_bind_shader(shader->version, instance_variant, base_spec_constants); + uint64_t spec_constants = base_spec_constants; + + if (inst->omni_light_count == 0) { + spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_OMNI; + } + + if (inst->spot_light_count == 0) { + spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_SPOT; + } + + if (prev_shader != shader || prev_variant != instance_variant || spec_constants != prev_spec_constants) { + bool success = material_storage->shaders.scene_shader.version_bind_shader(shader->version, instance_variant, spec_constants); if (!success) { continue; } @@ -2246,29 +2260,30 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, opaque_prepass_threshold = 0.1; } - material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::OPAQUE_PREPASS_THRESHOLD, opaque_prepass_threshold, shader->version, instance_variant, base_spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::OPAQUE_PREPASS_THRESHOLD, opaque_prepass_threshold, shader->version, instance_variant, spec_constants); prev_shader = shader; prev_variant = instance_variant; + prev_spec_constants = spec_constants; } if (prev_inst != inst || prev_shader != shader || prev_variant != instance_variant) { // Rebind the light indices. - material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::OMNI_LIGHT_COUNT, inst->omni_light_count, shader->version, instance_variant, base_spec_constants); - material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::SPOT_LIGHT_COUNT, inst->spot_light_count, shader->version, instance_variant, base_spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::OMNI_LIGHT_COUNT, inst->omni_light_count, shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::SPOT_LIGHT_COUNT, inst->spot_light_count, shader->version, instance_variant, spec_constants); if (inst->omni_light_count) { - glUniform1uiv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::OMNI_LIGHT_INDICES, shader->version, instance_variant, base_spec_constants), inst->omni_light_count, inst->omni_light_gl_cache.ptr()); + glUniform1uiv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::OMNI_LIGHT_INDICES, shader->version, instance_variant, spec_constants), inst->omni_light_count, inst->omni_light_gl_cache.ptr()); } if (inst->spot_light_count) { - glUniform1uiv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::SPOT_LIGHT_INDICES, shader->version, instance_variant, base_spec_constants), inst->spot_light_count, inst->spot_light_gl_cache.ptr()); + glUniform1uiv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::SPOT_LIGHT_INDICES, shader->version, instance_variant, spec_constants), inst->spot_light_count, inst->spot_light_gl_cache.ptr()); } prev_inst = inst; } - material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::WORLD_TRANSFORM, world_transform, shader->version, instance_variant, base_spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::WORLD_TRANSFORM, world_transform, shader->version, instance_variant, spec_constants); if (inst->instance_count > 0) { // Using MultiMesh or Particles. // Bind instance buffers. diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index 6e1f1babf8..255e62fc33 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -93,6 +93,7 @@ struct RenderDataGLES3 { Transform3D inv_cam_transform; Projection cam_projection; bool cam_orthogonal = false; + uint32_t camera_visible_layers = 0xFFFFFFFF; // For stereo rendering uint32_t view_count = 1; @@ -333,6 +334,10 @@ private: float fog_light_color[3]; float fog_sun_scatter; + uint32_t camera_visible_layers; + uint32_t pad1; + uint32_t pad2; + uint32_t pad3; }; static_assert(sizeof(UBO) % 16 == 0, "Scene UBO size must be a multiple of 16 bytes"); @@ -387,10 +392,10 @@ private: GeometryInstanceSurface **elements = nullptr; int element_count = 0; bool reverse_cull = false; - uint32_t spec_constant_base_flags = 0; + uint64_t spec_constant_base_flags = 0; bool force_wireframe = false; - RenderListParameters(GeometryInstanceSurface **p_elements, int p_element_count, bool p_reverse_cull, uint32_t p_spec_constant_base_flags, bool p_force_wireframe = false) { + RenderListParameters(GeometryInstanceSurface **p_elements, int p_element_count, bool p_reverse_cull, uint64_t p_spec_constant_base_flags, bool p_force_wireframe = false) { elements = p_elements; element_count = p_element_count; reverse_cull = p_reverse_cull; diff --git a/drivers/gles3/shader_gles3.cpp b/drivers/gles3/shader_gles3.cpp index 1dcd17ea0e..69c3ff7c8e 100644 --- a/drivers/gles3/shader_gles3.cpp +++ b/drivers/gles3/shader_gles3.cpp @@ -36,6 +36,11 @@ #include "core/io/dir_access.h" #include "core/io/file_access.h" +static String _mkid(const String &p_id) { + String id = "m_" + p_id.replace("__", "_dus_"); + return id.replace("__", "_dus_"); //doubleunderscore is reserved in glsl +} + void ShaderGLES3::_add_stage(const char *p_code, StageType p_stage_type) { Vector<String> lines = String(p_code).split("\n"); @@ -425,7 +430,7 @@ void ShaderGLES3::_compile_specialization(Version::Specialization &spec, uint32_ } // textures for (int i = 0; i < p_version->texture_uniforms.size(); i++) { - String native_uniform_name = p_version->texture_uniforms[i]; + String native_uniform_name = _mkid(p_version->texture_uniforms[i]); GLint location = glGetUniformLocation(spec.id, (native_uniform_name).ascii().get_data()); glUniform1i(location, i + base_texture_index); } diff --git a/drivers/gles3/shaders/canvas.glsl b/drivers/gles3/shaders/canvas.glsl index 60139de472..c1c26ed963 100644 --- a/drivers/gles3/shaders/canvas.glsl +++ b/drivers/gles3/shaders/canvas.glsl @@ -41,8 +41,6 @@ layout(std140) uniform MaterialUniforms{ //ubo:4 #include "canvas_uniforms_inc.glsl" #include "stdlib_inc.glsl" -uniform sampler2D transforms_texture; //texunit:-1 - out vec2 uv_interp; out vec4 color_interp; out vec2 vertex_interp; @@ -288,11 +286,9 @@ vec3 light_normal_compute(vec3 light_vec, vec3 normal, vec3 base_color, vec3 lig #endif -#define SHADOW_TEST(m_uv) \ - { \ - highp float sd = SHADOW_DEPTH(m_uv); \ - shadow += step(sd, shadow_uv.z / shadow_uv.w); \ - } +/* clang-format off */ +#define SHADOW_TEST(m_uv) { highp float sd = SHADOW_DEPTH(m_uv); shadow += step(sd, shadow_uv.z / shadow_uv.w); } +/* clang-format on */ //float distance = length(shadow_pos); vec4 light_shadow_compute(uint light_base, vec4 light_color, vec4 shadow_uv @@ -332,7 +328,7 @@ vec4 light_shadow_compute(uint light_base, vec4 light_color, vec4 shadow_uv shadow /= 13.0; } - vec4 shadow_color = unpackUnorm4x8(light_array[light_base].shadow_color); + vec4 shadow_color = godot_unpackUnorm4x8(light_array[light_base].shadow_color); #ifdef LIGHT_CODE_USED shadow_color.rgb *= shadow_modulate; #endif @@ -499,7 +495,7 @@ void main() { if (specular_shininess_used || (using_light && normal_used && bool(draw_data[draw_data_instance].flags & FLAGS_DEFAULT_SPECULAR_MAP_USED))) { specular_shininess = texture(specular_texture, uv); - specular_shininess *= unpackUnorm4x8(draw_data[draw_data_instance].specular_shininess); + specular_shininess *= godot_unpackUnorm4x8(draw_data[draw_data_instance].specular_shininess); specular_shininess_used = true; } else { specular_shininess = vec4(1.0); diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index adb4562750..fa68f0063f 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -129,7 +129,7 @@ layout(std140) uniform SceneData { // ubo:2 mediump float ambient_color_sky_mix; bool material_uv2_mode; - float pad2; + float emissive_exposure_normalization; bool use_ambient_light; bool use_ambient_cubemap; bool use_reflection_cubemap; @@ -142,7 +142,7 @@ layout(std140) uniform SceneData { // ubo:2 uint directional_light_count; float z_far; float z_near; - float pad; + float IBL_exposure_normalization; bool fog_enabled; float fog_density; @@ -151,6 +151,10 @@ layout(std140) uniform SceneData { // ubo:2 vec3 fog_light_color; float fog_sun_scatter; + uint camera_visible_layers; + uint pad3; + uint pad4; + uint pad5; } scene_data; @@ -455,7 +459,7 @@ layout(std140) uniform SceneData { // ubo:2 mediump float ambient_color_sky_mix; bool material_uv2_mode; - float pad2; + float emissive_exposure_normalization; bool use_ambient_light; bool use_ambient_cubemap; bool use_reflection_cubemap; @@ -468,7 +472,7 @@ layout(std140) uniform SceneData { // ubo:2 uint directional_light_count; float z_far; float z_near; - float pad; + float IBL_exposure_normalization; bool fog_enabled; float fog_density; @@ -477,6 +481,10 @@ layout(std140) uniform SceneData { // ubo:2 vec3 fog_light_color; float fog_sun_scatter; + uint camera_visible_layers; + uint pad3; + uint pad4; + uint pad5; } scene_data; @@ -771,7 +779,7 @@ float get_omni_attenuation(float distance, float inv_range, float decay) { nd *= nd; // nd^2 return nd * pow(max(distance, 0.0001), -decay); } - +#ifndef DISABLE_LIGHT_OMNI void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f0, float roughness, float metallic, float shadow, vec3 albedo, inout float alpha, #ifdef LIGHT_BACKLIGHT_USED vec3 backlight, @@ -813,7 +821,9 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f diffuse_light, specular_light); } +#endif // !DISABLE_LIGHT_OMNI +#ifndef DISABLE_LIGHT_SPOT void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f0, float roughness, float metallic, float shadow, vec3 albedo, inout float alpha, #ifdef LIGHT_BACKLIGHT_USED vec3 backlight, @@ -861,6 +871,7 @@ void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f #endif diffuse_light, specular_light); } +#endif // !DISABLE_LIGHT_SPOT #endif // !defined(DISABLE_LIGHT_DIRECTIONAL) || !defined(DISABLE_LIGHT_OMNI) && !defined(DISABLE_LIGHT_SPOT) #ifndef MODE_RENDER_DEPTH diff --git a/drivers/gles3/shaders/stdlib_inc.glsl b/drivers/gles3/shaders/stdlib_inc.glsl index d819940b1d..8d4a24cc1f 100644 --- a/drivers/gles3/shaders/stdlib_inc.glsl +++ b/drivers/gles3/shaders/stdlib_inc.glsl @@ -39,23 +39,32 @@ vec2 unpackSnorm2x16(uint p) { return clamp((v - 32767.0) * vec2(0.00003051851), vec2(-1.0), vec2(1.0)); } -uint packUnorm4x8(vec4 v) { +#endif + +// Compatibility renames. These are exposed with the "godot_" prefix +// to work around an Adreno bug which was exposing these ES310 functions +// in ES300 shaders. Internally, we must use the "godot_" prefix, but user shaders +// will be mapped automatically. +uint godot_packUnorm4x8(vec4 v) { uvec4 uv = uvec4(round(clamp(v, vec4(0.0), vec4(1.0)) * 255.0)); return uv.x | (uv.y << uint(8)) | (uv.z << uint(16)) | (uv.w << uint(24)); } -vec4 unpackUnorm4x8(uint p) { +vec4 godot_unpackUnorm4x8(uint p) { return vec4(float(p & uint(0xff)), float((p >> uint(8)) & uint(0xff)), float((p >> uint(16)) & uint(0xff)), float(p >> uint(24))) * 0.00392156862; // 1.0 / 255.0 } -uint packSnorm4x8(vec4 v) { +uint godot_packSnorm4x8(vec4 v) { uvec4 uv = uvec4(round(clamp(v, vec4(-1.0), vec4(1.0)) * 127.0) + 127.0); return uv.x | uv.y << uint(8) | uv.z << uint(16) | uv.w << uint(24); } -vec4 unpackSnorm4x8(uint p) { +vec4 godot_unpackSnorm4x8(uint p) { vec4 v = vec4(float(p & uint(0xff)), float((p >> uint(8)) & uint(0xff)), float((p >> uint(16)) & uint(0xff)), float(p >> uint(24))); return clamp((v - vec4(127.0)) * vec4(0.00787401574), vec4(-1.0), vec4(1.0)); } -#endif +#define packUnorm4x8 godot_packUnorm4x8 +#define unpackUnorm4x8 godot_unpackUnorm4x8 +#define packSnorm4x8 godot_packSnorm4x8 +#define unpackSnorm4x8 godot_unpackSnorm4x8 diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp index d413c2b00e..19e841f466 100644 --- a/drivers/gles3/storage/material_storage.cpp +++ b/drivers/gles3/storage/material_storage.cpp @@ -1522,6 +1522,7 @@ MaterialStorage::MaterialStorage() { actions.renames["NODE_POSITION_WORLD"] = "model_matrix[3].xyz"; actions.renames["CAMERA_POSITION_WORLD"] = "scene_data.inv_view_matrix[3].xyz"; actions.renames["CAMERA_DIRECTION_WORLD"] = "scene_data.view_matrix[3].xyz"; + actions.renames["CAMERA_VISIBLE_LAYERS"] = "scene_data.camera_visible_layers"; actions.renames["NODE_POSITION_VIEW"] = "(model_matrix * scene_data.view_matrix)[3].xyz"; actions.renames["VIEW_INDEX"] = "ViewIndex"; diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp index 285f32f1a5..5bbbc7b91b 100644 --- a/drivers/gles3/storage/mesh_storage.cpp +++ b/drivers/gles3/storage/mesh_storage.cpp @@ -334,7 +334,14 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) for (int i = 0; i < p_surface.bone_aabbs.size(); i++) { const AABB &bone = p_surface.bone_aabbs[i]; if (bone.has_volume()) { - mesh->bone_aabbs.write[i].merge_with(bone); + AABB &mesh_bone = mesh->bone_aabbs.write[i]; + if (mesh_bone != AABB()) { + // Already initialized, merge AABBs. + mesh_bone.merge_with(bone); + } else { + // Not yet initialized, copy the bone AABB. + mesh_bone = bone; + } } } mesh->aabb.merge_with(p_surface.aabb); diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index 028c7dca6f..ee251e7acf 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -378,9 +378,7 @@ Error VulkanContext::_obtain_vulkan_version() { uint32_t api_version; VkResult res = func(&api_version); if (res == VK_SUCCESS) { - vulkan_major = VK_API_VERSION_MAJOR(api_version); - vulkan_minor = VK_API_VERSION_MINOR(api_version); - vulkan_patch = VK_API_VERSION_PATCH(api_version); + instance_api_version = api_version; } else { // According to the documentation this shouldn't fail with anything except a memory allocation error // in which case we're in deep trouble anyway. @@ -388,13 +386,7 @@ Error VulkanContext::_obtain_vulkan_version() { } } else { print_line("vkEnumerateInstanceVersion not available, assuming Vulkan 1.0."); - } - - // We don't go above 1.2. - if ((vulkan_major > 1) || (vulkan_major == 1 && vulkan_minor > 2)) { - vulkan_major = 1; - vulkan_minor = 2; - vulkan_patch = 0; + instance_api_version = VK_API_VERSION_1_0; } return OK; @@ -832,7 +824,7 @@ Error VulkanContext::_check_capabilities() { VkPhysicalDeviceProperties2 physicalDeviceProperties{}; void *nextptr = nullptr; - if (!(vulkan_major == 1 && vulkan_minor == 0)) { + if (device_api_version >= VK_API_VERSION_1_1) { // Vulkan 1.1 or higher subgroupProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES; subgroupProperties.pNext = nextptr; @@ -937,15 +929,21 @@ Error VulkanContext::_create_instance() { enabled_extension_names[enabled_extension_count++] = extension_name.ptr(); } + // We'll set application version to the Vulkan version we're developing against, even if our instance is based on + // an older Vulkan version, devices can still support newer versions of Vulkan. + // The exception is when we're on Vulkan 1.0, we should not set this to anything but 1.0. + // Note that this value is only used by validation layers to warn us about version issues. + uint32_t application_api_version = instance_api_version == VK_API_VERSION_1_0 ? VK_API_VERSION_1_0 : VK_API_VERSION_1_2; + CharString cs = GLOBAL_GET("application/config/name").operator String().utf8(); const VkApplicationInfo app = { /*sType*/ VK_STRUCTURE_TYPE_APPLICATION_INFO, /*pNext*/ nullptr, /*pApplicationName*/ cs.get_data(), - /*applicationVersion*/ 0, + /*applicationVersion*/ 0, // It would be really nice if we store a version number in project settings, say "application/config/version" /*pEngineName*/ VERSION_NAME, /*engineVersion*/ VK_MAKE_VERSION(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH), - /*apiVersion*/ VK_MAKE_VERSION(vulkan_major, vulkan_minor, 0) + /*apiVersion*/ application_api_version }; VkInstanceCreateInfo inst_info{}; inst_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; @@ -1257,12 +1255,12 @@ Error VulkanContext::_create_physical_device(VkSurfaceKHR p_surface) { } } - print_line( - "Vulkan API " + itos(vulkan_major) + "." + itos(vulkan_minor) + "." + itos(vulkan_patch) + - " - " + "Using Vulkan Device #" + itos(device_index) + ": " + device_vendor + " - " + device_name); - + // Get device version device_api_version = gpu_props.apiVersion; + // Output our device version + print_line("Vulkan API " + get_device_api_version() + " - " + "Using Vulkan Device #" + itos(device_index) + ": " + device_vendor + " - " + device_name); + { Error _err = _initialize_device_extensions(); if (_err != OK) { @@ -1345,7 +1343,7 @@ Error VulkanContext::_create_device() { VkPhysicalDeviceVulkan11Features vulkan11features = {}; VkPhysicalDevice16BitStorageFeaturesKHR storage_feature = {}; VkPhysicalDeviceMultiviewFeatures multiview_features = {}; - if (vulkan_major > 1 || vulkan_minor >= 2) { + if (device_api_version >= VK_API_VERSION_1_2) { // In Vulkan 1.2 and newer we use a newer struct to enable various features. vulkan11features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; @@ -1373,7 +1371,7 @@ Error VulkanContext::_create_device() { storage_feature.storageInputOutput16 = storage_buffer_capabilities.storage_input_output_16; nextptr = &storage_feature; - if (vulkan_major == 1 && vulkan_minor == 1) { + if (device_api_version >= VK_API_VERSION_1_1) { // any Vulkan 1.1.x version multiview_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES; multiview_features.pNext = nextptr; multiview_features.multiview = multiview_capabilities.is_supported; @@ -2644,7 +2642,7 @@ RenderingDevice::DeviceType VulkanContext::get_device_type() const { } String VulkanContext::get_device_api_version() const { - return vformat("%d.%d.%d", vulkan_major, vulkan_minor, vulkan_patch); + return vformat("%d.%d.%d", VK_API_VERSION_MAJOR(device_api_version), VK_API_VERSION_MINOR(device_api_version), VK_API_VERSION_PATCH(device_api_version)); } String VulkanContext::get_device_pipeline_cache_uuid() const { diff --git a/drivers/vulkan/vulkan_context.h b/drivers/vulkan/vulkan_context.h index 0d49f5fe9f..f37bee72eb 100644 --- a/drivers/vulkan/vulkan_context.h +++ b/drivers/vulkan/vulkan_context.h @@ -110,10 +110,7 @@ private: bool device_initialized = false; bool inst_initialized = false; - // Vulkan 1.0 doesn't return version info so we assume this by default until we know otherwise. - uint32_t vulkan_major = 1; - uint32_t vulkan_minor = 0; - uint32_t vulkan_patch = 0; + uint32_t instance_api_version = VK_API_VERSION_1_0; SubgroupCapabilities subgroup_capabilities; MultiviewCapabilities multiview_capabilities; VRSCapabilities vrs_capabilities; @@ -276,8 +273,8 @@ public: bool supports_renderpass2() const { return is_device_extension_enabled(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME); } VkResult vkCreateRenderPass2KHR(VkDevice p_device, const VkRenderPassCreateInfo2 *p_create_info, const VkAllocationCallbacks *p_allocator, VkRenderPass *p_render_pass); - uint32_t get_vulkan_major() const { return vulkan_major; }; - uint32_t get_vulkan_minor() const { return vulkan_minor; }; + uint32_t get_vulkan_major() const { return VK_API_VERSION_MAJOR(device_api_version); }; + uint32_t get_vulkan_minor() const { return VK_API_VERSION_MINOR(device_api_version); }; const SubgroupCapabilities &get_subgroup_capabilities() const { return subgroup_capabilities; }; const MultiviewCapabilities &get_multiview_capabilities() const { return multiview_capabilities; }; const VRSCapabilities &get_vrs_capabilities() const { return vrs_capabilities; }; diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 7c403b7523..ecc465ef64 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -2134,10 +2134,9 @@ void AnimationTrackEdit::_notification(int p_what) { get_theme_icon(SNAME("InterpLinearAngle"), SNAME("EditorIcons")), get_theme_icon(SNAME("InterpCubicAngle"), SNAME("EditorIcons")), }; - Ref<Texture2D> cont_icon[4] = { + Ref<Texture2D> cont_icon[3] = { get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), - get_theme_icon(SNAME("TrackTrigger"), SNAME("EditorIcons")), get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")) }; @@ -2829,7 +2828,6 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { menu->clear(); menu->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS); menu->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), TTR("Discrete"), MENU_CALL_MODE_DISCRETE); - menu->add_icon_item(get_theme_icon(SNAME("TrackTrigger"), SNAME("EditorIcons")), TTR("Trigger"), MENU_CALL_MODE_TRIGGER); menu->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), TTR("Capture"), MENU_CALL_MODE_CAPTURE); menu->reset_size(); @@ -3194,7 +3192,6 @@ void AnimationTrackEdit::_menu_selected(int p_index) { switch (p_index) { case MENU_CALL_MODE_CONTINUOUS: case MENU_CALL_MODE_DISCRETE: - case MENU_CALL_MODE_TRIGGER: case MENU_CALL_MODE_CAPTURE: { Animation::UpdateMode update_mode = Animation::UpdateMode(p_index); Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo(); @@ -4322,10 +4319,6 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD h.type == Variant::TRANSFORM3D) { update_mode = Animation::UPDATE_CONTINUOUS; } - - if (h.usage & PROPERTY_USAGE_ANIMATE_AS_TRIGGER) { - update_mode = Animation::UPDATE_TRIGGER; - } } } @@ -4953,10 +4946,6 @@ void AnimationTrackEditor::_new_track_property_selected(String p_name) { h.type == Variant::TRANSFORM3D) { update_mode = Animation::UPDATE_CONTINUOUS; } - - if (h.usage & PROPERTY_USAGE_ANIMATE_AS_TRIGGER) { - update_mode = Animation::UPDATE_TRIGGER; - } } undo_redo->create_action(TTR("Add Track")); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index dc0c4abe5f..4b50424f39 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -138,7 +138,6 @@ class AnimationTrackEdit : public Control { enum { MENU_CALL_MODE_CONTINUOUS, MENU_CALL_MODE_DISCRETE, - MENU_CALL_MODE_TRIGGER, MENU_CALL_MODE_CAPTURE, MENU_INTERPOLATION_NEAREST, MENU_INTERPOLATION_LINEAR, diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 65cb083ac7..7ae0db7b48 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -33,6 +33,7 @@ #include "core/input/input.h" #include "core/os/keyboard.h" #include "core/string/string_builder.h" +#include "core/templates/pair.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/plugins/script_editor_plugin.h" @@ -872,6 +873,10 @@ void CodeTextEditor::_reset_zoom() { } void CodeTextEditor::_line_col_changed() { + if (!code_complete_timer->is_stopped() && code_complete_timer_line != text_editor->get_caret_line()) { + code_complete_timer->stop(); + } + String line = text_editor->get_line(text_editor->get_caret_line()); int positional_column = 0; @@ -901,6 +906,7 @@ void CodeTextEditor::_line_col_changed() { void CodeTextEditor::_text_changed() { if (text_editor->is_insert_text_operation()) { + code_complete_timer_line = text_editor->get_caret_line(); code_complete_timer->start(); } @@ -1290,90 +1296,98 @@ void CodeTextEditor::convert_case(CaseStyle p_case) { void CodeTextEditor::move_lines_up() { text_editor->begin_complex_operation(); - Vector<int> carets_to_remove; - Vector<int> caret_edit_order = text_editor->get_caret_index_edit_order(); + + // Lists of carets representing each group + Vector<Vector<int>> caret_groups; + Vector<Pair<int, int>> group_borders; + + // Search for groups of carets and their selections residing on the same lines for (int i = 0; i < caret_edit_order.size(); i++) { int c = caret_edit_order[i]; - int cl = text_editor->get_caret_line(c); - bool swaped_caret = false; - for (int j = i + 1; j < caret_edit_order.size(); j++) { - if (text_editor->has_selection(caret_edit_order[j])) { - if (text_editor->get_selection_from_line() == cl) { - carets_to_remove.push_back(caret_edit_order[j]); - continue; - } + Vector<int> new_group{ c }; + Pair<int, int> group_border; + if (text_editor->has_selection(c)) { + group_border.first = text_editor->get_selection_from_line(c); + group_border.second = text_editor->get_selection_to_line(c); + } else { + group_border.first = text_editor->get_caret_line(c); + group_border.second = text_editor->get_caret_line(c); + } - if (text_editor->get_selection_to_line() == cl) { - if (text_editor->has_selection(c)) { - if (text_editor->get_selection_to_line(c) != cl) { - text_editor->select(cl + 1, 0, text_editor->get_selection_to_line(c), text_editor->get_selection_to_column(c), c); - break; - } - } + for (int j = i; j < caret_edit_order.size() - 1; j++) { + int c_current = caret_edit_order[j]; + int c_next = caret_edit_order[j + 1]; - carets_to_remove.push_back(c); - i = j - 1; - swaped_caret = true; - break; - } + int next_start_pos = text_editor->has_selection(c_next) ? text_editor->get_selection_from_line(c_next) : text_editor->get_caret_line(c_next); + int next_end_pos = text_editor->has_selection(c_next) ? text_editor->get_selection_to_line(c_next) : text_editor->get_caret_line(c_next); + + int current_start_pos = text_editor->has_selection(c_current) ? text_editor->get_selection_from_line(c_current) : text_editor->get_caret_line(c_current); + + i = j; + if (next_end_pos != current_start_pos && next_end_pos + 1 != current_start_pos) { break; } - - if (text_editor->get_caret_line(caret_edit_order[j]) == cl) { - carets_to_remove.push_back(caret_edit_order[j]); - i = j; - continue; + group_border.first = next_start_pos; + new_group.push_back(c_next); + // If the last caret is added to the current group there is no need to process it again + if (j + 1 == caret_edit_order.size() - 1) { + i++; } - break; } + group_borders.push_back(group_border); + caret_groups.push_back(new_group); + } - if (swaped_caret) { + for (int i = group_borders.size() - 1; i >= 0; i--) { + if (group_borders[i].first - 1 < 0) { continue; } - if (text_editor->has_selection(c)) { + // If the group starts overlapping with the upper group don't move it + if (i < group_borders.size() - 1 && group_borders[i].first - 1 <= group_borders[i + 1].second) { + continue; + } + + // We have to remember caret positions and selections prior to line swapping + Vector<Vector<int>> caret_group_parameters; + + for (int j = 0; j < caret_groups[i].size(); j++) { + int c = caret_groups[i][j]; + int cursor_line = text_editor->get_caret_line(c); + int cursor_column = text_editor->get_caret_column(c); + + if (!text_editor->has_selection(c)) { + caret_group_parameters.push_back(Vector<int>{ -1, -1, -1, -1, cursor_line, cursor_column }); + continue; + } int from_line = text_editor->get_selection_from_line(c); int from_col = text_editor->get_selection_from_column(c); int to_line = text_editor->get_selection_to_line(c); int to_column = text_editor->get_selection_to_column(c); - int cursor_line = text_editor->get_caret_line(c); + caret_group_parameters.push_back(Vector<int>{ from_line, from_col, to_line, to_column, cursor_line, cursor_column }); + } - for (int j = from_line; j <= to_line; j++) { - int line_id = j; - int next_id = j - 1; + for (int line_id = group_borders[i].first; line_id <= group_borders[i].second; line_id++) { + text_editor->unfold_line(line_id); + text_editor->unfold_line(line_id - 1); - if (line_id == 0 || next_id < 0) { - return; - } + text_editor->swap_lines(line_id - 1, line_id); + } - text_editor->unfold_line(line_id); - text_editor->unfold_line(next_id); + for (int j = 0; j < caret_groups[i].size(); j++) { + int c = caret_groups[i][j]; + Vector<int> caret_parameters = caret_group_parameters[j]; + text_editor->set_caret_line(caret_parameters[4] - 1, c == 0, true, 0, c); + text_editor->set_caret_column(caret_parameters[5], c == 0, c); - text_editor->swap_lines(line_id, next_id); - text_editor->set_caret_line(next_id, c == 0, true, 0, c); + if (caret_parameters[0] >= 0) { + text_editor->select(caret_parameters[0] - 1, caret_parameters[1], caret_parameters[2] - 1, caret_parameters[3], c); } - int from_line_up = from_line > 0 ? from_line - 1 : from_line; - int to_line_up = to_line > 0 ? to_line - 1 : to_line; - int cursor_line_up = cursor_line > 0 ? cursor_line - 1 : cursor_line; - text_editor->select(from_line_up, from_col, to_line_up, to_column, c); - text_editor->set_caret_line(cursor_line_up, c == 0, true, 0, c); - } else { - int line_id = text_editor->get_caret_line(c); - int next_id = line_id - 1; - - if (line_id == 0 || next_id < 0) { - return; - } - - text_editor->unfold_line(line_id); - text_editor->unfold_line(next_id); - - text_editor->swap_lines(line_id, next_id); - text_editor->set_caret_line(next_id, c == 0, true, 0, c); } } + text_editor->end_complex_operation(); text_editor->merge_overlapping_carets(); text_editor->queue_redraw(); @@ -1382,95 +1396,97 @@ void CodeTextEditor::move_lines_up() { void CodeTextEditor::move_lines_down() { text_editor->begin_complex_operation(); - Vector<int> carets_to_remove; - Vector<int> caret_edit_order = text_editor->get_caret_index_edit_order(); + + // Lists of carets representing each group + Vector<Vector<int>> caret_groups; + Vector<Pair<int, int>> group_borders; + + // Search for groups of carets and their selections residing on the same lines for (int i = 0; i < caret_edit_order.size(); i++) { int c = caret_edit_order[i]; - int cl = text_editor->get_caret_line(c); - bool swaped_caret = false; - for (int j = i + 1; j < caret_edit_order.size(); j++) { - if (text_editor->has_selection(caret_edit_order[j])) { - if (text_editor->get_selection_from_line() == cl) { - carets_to_remove.push_back(caret_edit_order[j]); - continue; - } + Vector<int> new_group{ c }; + Pair<int, int> group_border; + if (text_editor->has_selection(c)) { + group_border.first = text_editor->get_selection_from_line(c); + group_border.second = text_editor->get_selection_to_line(c); + } else { + group_border.first = text_editor->get_caret_line(c); + group_border.second = text_editor->get_caret_line(c); + } - if (text_editor->get_selection_to_line() == cl) { - if (text_editor->has_selection(c)) { - if (text_editor->get_selection_to_line(c) != cl) { - text_editor->select(cl + 1, 0, text_editor->get_selection_to_line(c), text_editor->get_selection_to_column(c), c); - break; - } - } + for (int j = i; j < caret_edit_order.size() - 1; j++) { + int c_current = caret_edit_order[j]; + int c_next = caret_edit_order[j + 1]; - carets_to_remove.push_back(c); - i = j - 1; - swaped_caret = true; - break; + int next_start_pos = text_editor->has_selection(c_next) ? text_editor->get_selection_from_line(c_next) : text_editor->get_caret_line(c_next); + int next_end_pos = text_editor->has_selection(c_next) ? text_editor->get_selection_to_line(c_next) : text_editor->get_caret_line(c_next); + + int current_start_pos = text_editor->has_selection(c_current) ? text_editor->get_selection_from_line(c_current) : text_editor->get_caret_line(c_current); + + i = j; + if (next_end_pos == current_start_pos || next_end_pos + 1 == current_start_pos) { + group_border.first = next_start_pos; + new_group.push_back(c_next); + // If the last caret is added to the current group there is no need to process it again + if (j + 1 == caret_edit_order.size() - 1) { + i++; } + } else { break; } - - if (text_editor->get_caret_line(caret_edit_order[j]) == cl) { - carets_to_remove.push_back(caret_edit_order[j]); - i = j; - continue; - } - break; } + group_borders.push_back(group_border); + caret_groups.push_back(new_group); + } - if (swaped_caret) { + for (int i = 0; i < group_borders.size(); i++) { + if (group_borders[i].second + 1 > text_editor->get_line_count() - 1) { continue; } - if (text_editor->has_selection(c)) { - int from_line = text_editor->get_selection_from_line(c); - int from_col = text_editor->get_selection_from_column(c); - int to_line = text_editor->get_selection_to_line(c); - int to_column = text_editor->get_selection_to_column(c); - int cursor_line = text_editor->get_caret_line(c); - - for (int l = to_line; l >= from_line; l--) { - int line_id = l; - int next_id = l + 1; - - if (line_id == text_editor->get_line_count() - 1 || next_id > text_editor->get_line_count()) { - continue; - } - - text_editor->unfold_line(line_id); - text_editor->unfold_line(next_id); + // If the group starts overlapping with the upper group don't move it + if (i > 0 && group_borders[i].second + 1 >= group_borders[i - 1].first) { + continue; + } - text_editor->swap_lines(line_id, next_id); - text_editor->set_caret_line(next_id, c == 0, true, 0, c); - } - int from_line_down = from_line < text_editor->get_line_count() ? from_line + 1 : from_line; - int to_line_down = to_line < text_editor->get_line_count() ? to_line + 1 : to_line; - int cursor_line_down = cursor_line < text_editor->get_line_count() ? cursor_line + 1 : cursor_line; - text_editor->select(from_line_down, from_col, to_line_down, to_column, c); - text_editor->set_caret_line(cursor_line_down, c == 0, true, 0, c); - } else { - int line_id = text_editor->get_caret_line(c); - int next_id = line_id + 1; + // We have to remember caret positions and selections prior to line swapping + Vector<Vector<int>> caret_group_parameters; - if (line_id == text_editor->get_line_count() - 1 || next_id > text_editor->get_line_count()) { - continue; + for (int j = 0; j < caret_groups[i].size(); j++) { + int c = caret_groups[i][j]; + int cursor_line = text_editor->get_caret_line(c); + int cursor_column = text_editor->get_caret_column(c); + + if (text_editor->has_selection(c)) { + int from_line = text_editor->get_selection_from_line(c); + int from_col = text_editor->get_selection_from_column(c); + int to_line = text_editor->get_selection_to_line(c); + int to_column = text_editor->get_selection_to_column(c); + caret_group_parameters.push_back(Vector<int>{ from_line, from_col, to_line, to_column, cursor_line, cursor_column }); + } else { + caret_group_parameters.push_back(Vector<int>{ -1, -1, -1, -1, cursor_line, cursor_column }); } + } + for (int line_id = group_borders[i].second; line_id >= group_borders[i].first; line_id--) { text_editor->unfold_line(line_id); - text_editor->unfold_line(next_id); + text_editor->unfold_line(line_id + 1); - text_editor->swap_lines(line_id, next_id); - text_editor->set_caret_line(next_id, c == 0, true, 0, c); + text_editor->swap_lines(line_id + 1, line_id); } - } - // Sort and remove backwards to preserve indexes. - carets_to_remove.sort(); - for (int i = carets_to_remove.size() - 1; i >= 0; i--) { - text_editor->remove_caret(carets_to_remove[i]); + for (int j = 0; j < caret_groups[i].size(); j++) { + int c = caret_groups[i][j]; + Vector<int> caret_parameters = caret_group_parameters[j]; + text_editor->set_caret_line(caret_parameters[4] + 1, c == 0, true, 0, c); + text_editor->set_caret_column(caret_parameters[5], c == 0, c); + + if (caret_parameters[0] >= 0) { + text_editor->select(caret_parameters[0] + 1, caret_parameters[1], caret_parameters[2] + 1, caret_parameters[3], c); + } + } } text_editor->merge_overlapping_carets(); diff --git a/editor/code_editor.h b/editor/code_editor.h index ded7518287..4d7034fbd0 100644 --- a/editor/code_editor.h +++ b/editor/code_editor.h @@ -158,6 +158,7 @@ class CodeTextEditor : public VBoxContainer { Label *info = nullptr; Timer *idle = nullptr; Timer *code_complete_timer = nullptr; + int code_complete_timer_line = 0; Timer *font_resize_timer = nullptr; int font_resize_val; diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index 785476d75b..2adab089e4 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -275,7 +275,8 @@ void CreateDialog::_configure_search_option_item(TreeItem *r_item, const String r_item->set_text(0, "\"" + p_type + "\""); } else if (script_type) { r_item->set_metadata(0, p_type); - r_item->set_text(0, p_type + " (" + ScriptServer::get_global_class_path(p_type).get_file() + ")"); + r_item->set_text(0, p_type); + r_item->set_suffix(0, "(" + ScriptServer::get_global_class_path(p_type).get_file() + ")"); } else { r_item->set_metadata(0, custom_type_parents[p_type]); r_item->set_text(0, p_type); diff --git a/editor/editor_command_palette.cpp b/editor/editor_command_palette.cpp index b92b0fca59..609afeb40a 100644 --- a/editor/editor_command_palette.cpp +++ b/editor/editor_command_palette.cpp @@ -38,6 +38,9 @@ EditorCommandPalette *EditorCommandPalette::singleton = nullptr; +static Rect2i prev_rect = Rect2i(); +static bool was_showed = false; + float EditorCommandPalette::_score_path(const String &p_search, const String &p_path) { float score = 0.9f + .1f * (p_search.length() / (float)p_path.length()); @@ -145,6 +148,17 @@ void EditorCommandPalette::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_command", "key_name"), &EditorCommandPalette::remove_command); } +void EditorCommandPalette::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible()) { + prev_rect = Rect2i(get_position(), get_size()); + was_showed = true; + } + } break; + } +} + void EditorCommandPalette::_sbox_input(const Ref<InputEvent> &p_ie) { Ref<InputEventKey> k = p_ie; if (k.is_valid()) { @@ -171,12 +185,10 @@ void EditorCommandPalette::_confirmed() { } void EditorCommandPalette::open_popup() { - static bool was_showed = false; - if (!was_showed) { - was_showed = true; - popup_centered_clamped(Size2(600, 440) * EDSCALE, 0.8f); + if (was_showed) { + popup(prev_rect); } else { - show(); + popup_centered_clamped(Size2(600, 440) * EDSCALE, 0.8f); } command_search_box->clear(); diff --git a/editor/editor_command_palette.h b/editor/editor_command_palette.h index 15200552b4..81cf401851 100644 --- a/editor/editor_command_palette.h +++ b/editor/editor_command_palette.h @@ -91,6 +91,7 @@ class EditorCommandPalette : public ConfirmationDialog { protected: static void _bind_methods(); + void _notification(int p_what); public: void open_popup(); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 5d137ce290..678ec04b9d 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -1642,6 +1642,7 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector String importer_name; HashMap<String, HashMap<StringName, Variant>> source_file_options; + HashMap<String, ResourceUID::ID> uids; HashMap<String, String> base_paths; for (int i = 0; i < p_files.size(); i++) { Ref<ConfigFile> config; @@ -1657,6 +1658,15 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector ERR_FAIL_V(ERR_FILE_CORRUPT); } + ResourceUID::ID uid = ResourceUID::INVALID_ID; + + if (config->has_section_key("remap", "uid")) { + String uidt = config->get_value("remap", "uid"); + uid = ResourceUID::get_singleton()->text_to_id(uidt); + } + + uids[p_files[i]] = uid; + source_file_options[p_files[i]] = HashMap<StringName, Variant>(); importer_name = file_importer_name; @@ -1701,6 +1711,7 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector const String &file = E.key; String base_path = ResourceFormatImporter::get_singleton()->get_import_base_path(file); Vector<String> dest_paths; + ResourceUID::ID uid = uids[file]; { Ref<FileAccess> f = FileAccess::open(file + ".import", FileAccess::WRITE); ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Cannot open import file '" + file + ".import'."); @@ -1717,6 +1728,12 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector f->store_line("type=\"" + importer->get_resource_type() + "\""); } + if (uid == ResourceUID::INVALID_ID) { + uid = ResourceUID::get_singleton()->create_id(); + } + + f->store_line("uid=\"" + ResourceUID::get_singleton()->id_to_text(uid) + "\""); // Store in readable format. + if (err == OK) { String path = base_path + "." + importer->get_save_extension(); f->store_line("path=\"" + path + "\""); @@ -1782,12 +1799,19 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector fs->files[cpos]->modified_time = FileAccess::get_modified_time(file); fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(file + ".import"); fs->files[cpos]->deps = _get_dependencies(file); + fs->files[cpos]->uid = uid; fs->files[cpos]->type = importer->get_resource_type(); if (fs->files[cpos]->type == "" && textfile_extensions.has(file.get_extension())) { fs->files[cpos]->type = "TextFile"; } fs->files[cpos]->import_valid = err == OK; + if (ResourceUID::get_singleton()->has_id(uid)) { + ResourceUID::get_singleton()->set_id(uid, file); + } else { + ResourceUID::get_singleton()->add_id(uid, file); + } + //if file is currently up, maybe the source it was loaded from changed, so import math must be updated for it //to reload properly Ref<Resource> r = ResourceCache::get_ref(file); @@ -2074,6 +2098,8 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) { importing = true; EditorProgress pr("reimport", TTR("(Re)Importing Assets"), p_files.size()); + Vector<String> reloads; + Vector<ImportFile> reimport_files; HashSet<String> groups_to_reimport; @@ -2089,22 +2115,26 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) { String group_file = ResourceFormatImporter::get_singleton()->get_import_group_file(file); if (group_file_cache.has(file)) { - //maybe the file itself is a group! + // Maybe the file itself is a group! groups_to_reimport.insert(file); - //groups do not belong to groups + // Groups do not belong to groups. + group_file = String(); + } else if (groups_to_reimport.has(file)) { + // Groups do not belong to groups. group_file = String(); } else if (!group_file.is_empty()) { - //it's a group file, add group to import and skip this file + // It's a group file, add group to import and skip this file. groups_to_reimport.insert(group_file); } else { - //it's a regular file + // It's a regular file. ImportFile ifile; ifile.path = file; ResourceFormatImporter::get_singleton()->get_import_order_threads_and_importer(file, ifile.order, ifile.threaded, ifile.importer); + reloads.push_back(file); reimport_files.push_back(ifile); } - //group may have changed, so also update group reference + // Group may have changed, so also update group reference. EditorFileSystemDirectory *fs = nullptr; int cpos = -1; if (_find_file(file, &fs, cpos)) { @@ -2118,10 +2148,14 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) { int from = 0; for (int i = 0; i < reimport_files.size(); i++) { + if (groups_to_reimport.has(reimport_files[i].path)) { + continue; + } + if (use_multiple_threads && reimport_files[i].threaded) { if (i + 1 == reimport_files.size() || reimport_files[i + 1].importer != reimport_files[from].importer) { if (from - i == 0) { - //single file, do not use threads + // Single file, do not use threads. pr.step(reimport_files[i].path.get_file(), i); _reimport_file(reimport_files[i].path); } else { @@ -2159,20 +2193,25 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) { } } - //reimport groups + // Reimport groups. + + from = reimport_files.size(); if (groups_to_reimport.size()) { HashMap<String, Vector<String>> group_files; _find_group_files(filesystem, group_files, groups_to_reimport); for (const KeyValue<String, Vector<String>> &E : group_files) { + pr.step(E.key.get_file(), from++); Error err = _reimport_group(E.key, E.value); + reloads.push_back(E.key); + reloads.append_array(E.value); if (err == OK) { _reimport_file(E.key); } } } - ResourceUID::get_singleton()->update_cache(); //after reimporting, update the cache + ResourceUID::get_singleton()->update_cache(); // After reimporting, update the cache. _save_filesystem_cache(); importing = false; @@ -2180,7 +2219,7 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) { emit_signal(SNAME("filesystem_changed")); } - emit_signal(SNAME("resources_reimported"), p_files); + emit_signal(SNAME("resources_reimported"), reloads); } Error EditorFileSystem::_resource_import(const String &p_path) { diff --git a/editor/editor_fonts.cpp b/editor/editor_fonts.cpp index e7d4636ad9..cf413133aa 100644 --- a/editor/editor_fonts.cpp +++ b/editor/editor_fonts.cpp @@ -41,7 +41,7 @@ Ref<FontFile> load_external_font(const String &p_path, TextServer::Hinting p_hin Ref<FontFile> font; font.instantiate(); - Vector<uint8_t> data = FileAccess::get_file_as_array(p_path); + Vector<uint8_t> data = FileAccess::get_file_as_bytes(p_path); font->set_data(data); font->set_multichannel_signed_distance_field(p_msdf); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 06e1e8b22e..463e8f6bdc 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -6038,14 +6038,7 @@ EditorNode::EditorNode() { Input *id = Input::get_singleton(); if (id) { - bool found_touchscreen = false; - for (int i = 0; i < DisplayServer::get_singleton()->get_screen_count(); i++) { - if (DisplayServer::get_singleton()->screen_is_touchscreen(i)) { - found_touchscreen = true; - } - } - - if (!found_touchscreen && Input::get_singleton()) { + if (!DisplayServer::get_singleton()->is_touchscreen_available() && Input::get_singleton()) { // Only if no touchscreen ui hint, disable emulation just in case. id->set_emulate_touch_from_mouse(false); } diff --git a/editor/editor_quick_open.cpp b/editor/editor_quick_open.cpp index bb533b88d6..6ddccba0e2 100644 --- a/editor/editor_quick_open.cpp +++ b/editor/editor_quick_open.cpp @@ -34,23 +34,24 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" -void EditorQuickOpen::popup_dialog(const String &p_base, bool p_enable_multi, bool p_dontclear) { +static Rect2i prev_rect = Rect2i(); +static bool was_showed = false; + +void EditorQuickOpen::popup_dialog(const String &p_base, bool p_enable_multi, bool p_dont_clear) { base_type = p_base; allow_multi_select = p_enable_multi; search_options->set_select_mode(allow_multi_select ? Tree::SELECT_MULTI : Tree::SELECT_SINGLE); - static bool was_showed = false; - if (!was_showed) { - was_showed = true; - popup_centered_clamped(Size2(600, 440) * EDSCALE, 0.8f); + if (was_showed) { + popup(prev_rect); } else { - show(); + popup_centered_clamped(Size2(600, 440) * EDSCALE, 0.8f); } EditorFileSystemDirectory *efsd = EditorFileSystem::get_singleton()->get_filesystem(); _build_search_cache(efsd); - if (p_dontclear) { + if (p_dont_clear) { search_box->select_all(); _update_search(); } else { @@ -251,6 +252,13 @@ void EditorQuickOpen::_notification(int p_what) { search_box->set_clear_button_enabled(true); } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible()) { + prev_rect = Rect2i(get_position(), get_size()); + was_showed = true; + } + } break; + case NOTIFICATION_EXIT_TREE: { disconnect("confirmed", callable_mp(this, &EditorQuickOpen::_confirmed)); } break; diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp index 11a8fce9c3..9128143619 100644 --- a/editor/editor_spin_slider.cpp +++ b/editor/editor_spin_slider.cpp @@ -582,6 +582,7 @@ void EditorSpinSlider::_value_focus_exited() { //tab was pressed } else { //enter, click, esc + grab_focus(); } } diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index 7c5c7da2ef..c01db215da 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -442,10 +442,11 @@ HashSet<String> EditorExportPlatform::get_features(const Ref<EditorExportPreset> result.insert(E); } + result.insert("template"); if (p_debug) { - result.insert("debug"); + result.insert("template_debug"); } else { - result.insert("release"); + result.insert("template_release"); } if (!p_preset->get_custom_features().is_empty()) { @@ -1034,7 +1035,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & return err; } // Now actual remapped file: - sarr = FileAccess::get_file_as_array(export_path); + sarr = FileAccess::get_file_as_bytes(export_path); err = p_func(p_udata, export_path, sarr, idx, total, enc_in_filters, enc_ex_filters, key); if (err != OK) { return err; @@ -1053,7 +1054,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & if (importer_type == "keep") { //just keep file as-is - Vector<uint8_t> array = FileAccess::get_file_as_array(path); + Vector<uint8_t> array = FileAccess::get_file_as_bytes(path); err = p_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key); if (err != OK) { @@ -1086,14 +1087,14 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & String remap = F; if (remap == "path") { String remapped_path = config->get_value("remap", remap); - Vector<uint8_t> array = FileAccess::get_file_as_array(remapped_path); + Vector<uint8_t> array = FileAccess::get_file_as_bytes(remapped_path); err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key); } else if (remap.begins_with("path.")) { String feature = remap.get_slice(".", 1); if (remap_features.has(feature)) { String remapped_path = config->get_value("remap", remap); - Vector<uint8_t> array = FileAccess::get_file_as_array(remapped_path); + Vector<uint8_t> array = FileAccess::get_file_as_bytes(remapped_path); err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key); } } @@ -1104,7 +1105,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & } //also save the .import file - Vector<uint8_t> array = FileAccess::get_file_as_array(path + ".import"); + Vector<uint8_t> array = FileAccess::get_file_as_bytes(path + ".import"); err = p_func(p_udata, path + ".import", array, idx, total, enc_in_filters, enc_ex_filters, key); if (err != OK) { @@ -1164,7 +1165,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & path_remaps.push_back(export_path); } - Vector<uint8_t> array = FileAccess::get_file_as_array(export_path); + Vector<uint8_t> array = FileAccess::get_file_as_bytes(export_path); err = p_func(p_udata, export_path, array, idx, total, enc_in_filters, enc_ex_filters, key); if (err != OK) { return err; @@ -1244,14 +1245,14 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & String icon = GLOBAL_GET("application/config/icon"); String splash = GLOBAL_GET("application/boot_splash/image"); if (!icon.is_empty() && FileAccess::exists(icon)) { - Vector<uint8_t> array = FileAccess::get_file_as_array(icon); + Vector<uint8_t> array = FileAccess::get_file_as_bytes(icon); err = p_func(p_udata, icon, array, idx, total, enc_in_filters, enc_ex_filters, key); if (err != OK) { return err; } } if (!splash.is_empty() && FileAccess::exists(splash) && icon != splash) { - Vector<uint8_t> array = FileAccess::get_file_as_array(splash); + Vector<uint8_t> array = FileAccess::get_file_as_bytes(splash); err = p_func(p_udata, splash, array, idx, total, enc_in_filters, enc_ex_filters, key); if (err != OK) { return err; @@ -1259,7 +1260,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & } String resource_cache_file = ResourceUID::get_cache_file(); if (FileAccess::exists(resource_cache_file)) { - Vector<uint8_t> array = FileAccess::get_file_as_array(resource_cache_file); + Vector<uint8_t> array = FileAccess::get_file_as_bytes(resource_cache_file); err = p_func(p_udata, resource_cache_file, array, idx, total, enc_in_filters, enc_ex_filters, key); if (err != OK) { return err; @@ -1268,7 +1269,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & String extension_list_config_file = NativeExtension::get_extension_list_config_file(); if (FileAccess::exists(extension_list_config_file)) { - Vector<uint8_t> array = FileAccess::get_file_as_array(extension_list_config_file); + Vector<uint8_t> array = FileAccess::get_file_as_bytes(extension_list_config_file); err = p_func(p_udata, extension_list_config_file, array, idx, total, enc_in_filters, enc_ex_filters, key); if (err != OK) { return err; @@ -1282,7 +1283,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & // Try using user provided data file. String ts_data = "res://" + TS->get_support_data_filename(); if (FileAccess::exists(ts_data)) { - Vector<uint8_t> array = FileAccess::get_file_as_array(ts_data); + Vector<uint8_t> array = FileAccess::get_file_as_bytes(ts_data); err = p_func(p_udata, ts_data, array, idx, total, enc_in_filters, enc_ex_filters, key); if (err != OK) { return err; @@ -1291,7 +1292,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & // Use default text server data. String icu_data_file = EditorPaths::get_singleton()->get_cache_dir().path_join("tmp_icu_data"); TS->save_support_data(icu_data_file); - Vector<uint8_t> array = FileAccess::get_file_as_array(icu_data_file); + Vector<uint8_t> array = FileAccess::get_file_as_bytes(icu_data_file); err = p_func(p_udata, ts_data, array, idx, total, enc_in_filters, enc_ex_filters, key); DirAccess::remove_file_or_error(icu_data_file); if (err != OK) { @@ -1304,7 +1305,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & String config_file = "project.binary"; String engine_cfb = EditorPaths::get_singleton()->get_cache_dir().path_join("tmp" + config_file); ProjectSettings::get_singleton()->save_custom(engine_cfb, custom_map, custom_list); - Vector<uint8_t> data = FileAccess::get_file_as_array(engine_cfb); + Vector<uint8_t> data = FileAccess::get_file_as_bytes(engine_cfb); DirAccess::remove_file_or_error(engine_cfb); return p_func(p_udata, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, key); diff --git a/editor/export/editor_export_platform_pc.cpp b/editor/export/editor_export_platform_pc.cpp index 9de2f94900..5345346c48 100644 --- a/editor/export/editor_export_platform_pc.cpp +++ b/editor/export/editor_export_platform_pc.cpp @@ -81,8 +81,8 @@ bool EditorExportPlatformPC::has_valid_export_configuration(const Ref<EditorExpo // Look for export templates (first official, and if defined custom templates). String arch = p_preset->get("binary_format/architecture"); - bool dvalid = exists_export_template(get_template_file_name("debug", arch), &err); - bool rvalid = exists_export_template(get_template_file_name("release", arch), &err); + bool dvalid = exists_export_template(get_template_file_name("template_debug", arch), &err); + bool rvalid = exists_export_template(get_template_file_name("template_release", arch), &err); if (p_preset->get("custom_template/debug") != "") { dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); diff --git a/editor/icons/TrackTrigger.svg b/editor/icons/TrackTrigger.svg deleted file mode 100644 index c403fba59a..0000000000 --- a/editor/icons/TrackTrigger.svg +++ /dev/null @@ -1 +0,0 @@ -<svg height="8" viewBox="0 0 16 8" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m1 1v2h2v4h2v-4h2v-2zm13 0a1 1 0 0 0 -1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0 -1-1zm-3 2a1 1 0 0 0 -1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0 -1-1zm-3 2a1 1 0 0 0 -1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0 -1-1z" fill="#f68f45"/></svg> diff --git a/editor/import/dynamic_font_import_settings.cpp b/editor/import/dynamic_font_import_settings.cpp index 0aa77f6ea0..75d0abbb0c 100644 --- a/editor/import/dynamic_font_import_settings.cpp +++ b/editor/import/dynamic_font_import_settings.cpp @@ -469,6 +469,8 @@ void DynamicFontImportSettings::_main_prop_changed(const String &p_edited_proper font_preview->set_msdf_pixel_range(import_settings_data->get("msdf_pixel_range")); } else if (p_edited_property == "msdf_size") { font_preview->set_msdf_size(import_settings_data->get("msdf_size")); + } else if (p_edited_property == "allow_system_fallback") { + font_preview->set_allow_system_fallback(import_settings_data->get("allow_system_fallback")); } else if (p_edited_property == "force_autohinter") { font_preview->set_force_autohinter(import_settings_data->get("force_autohinter")); } else if (p_edited_property == "hinting") { @@ -936,6 +938,7 @@ void DynamicFontImportSettings::_re_import() { main_settings["multichannel_signed_distance_field"] = import_settings_data->get("multichannel_signed_distance_field"); main_settings["msdf_pixel_range"] = import_settings_data->get("msdf_pixel_range"); main_settings["msdf_size"] = import_settings_data->get("msdf_size"); + main_settings["allow_system_fallback"] = import_settings_data->get("allow_system_fallback"); main_settings["force_autohinter"] = import_settings_data->get("force_autohinter"); main_settings["hinting"] = import_settings_data->get("hinting"); main_settings["subpixel_positioning"] = import_settings_data->get("subpixel_positioning"); @@ -1036,7 +1039,7 @@ void DynamicFontImportSettings::_process_locales() { void DynamicFontImportSettings::open_settings(const String &p_path) { // Load base font data. - Vector<uint8_t> font_data = FileAccess::get_file_as_array(p_path); + Vector<uint8_t> font_data = FileAccess::get_file_as_bytes(p_path); // Load project locale list. locale_tree->clear(); @@ -1202,6 +1205,7 @@ void DynamicFontImportSettings::open_settings(const String &p_path) { font_preview->set_multichannel_signed_distance_field(import_settings_data->get("multichannel_signed_distance_field")); font_preview->set_msdf_pixel_range(import_settings_data->get("msdf_pixel_range")); font_preview->set_msdf_size(import_settings_data->get("msdf_size")); + font_preview->set_allow_system_fallback(import_settings_data->get("allow_system_fallback")); font_preview->set_force_autohinter(import_settings_data->get("force_autohinter")); font_preview->set_hinting((TextServer::Hinting)import_settings_data->get("hinting").operator int()); font_preview->set_subpixel_positioning((TextServer::SubpixelPositioning)import_settings_data->get("subpixel_positioning").operator int()); @@ -1232,6 +1236,7 @@ DynamicFontImportSettings::DynamicFontImportSettings() { options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true)); options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_RANGE, "1,100,1"), 8)); options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_RANGE, "1,250,1"), 48)); + options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "allow_system_fallback"), true)); options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "force_autohinter"), false)); options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), 1)); options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One Half of a Pixel,One Quarter of a Pixel"), 1)); diff --git a/editor/import/resource_importer_bmfont.cpp b/editor/import/resource_importer_bmfont.cpp index 4fb1b726bd..357faf0022 100644 --- a/editor/import/resource_importer_bmfont.cpp +++ b/editor/import/resource_importer_bmfont.cpp @@ -76,6 +76,7 @@ Error ResourceImporterBMFont::import(const String &p_source_file, const String & Error err = font->load_bitmap_font(p_source_file); ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load font to file \"" + p_source_file + "\"."); + font->set_allow_system_fallback(false); font->set_fallbacks(fallbacks); int flg = 0; diff --git a/editor/import/resource_importer_dynamic_font.cpp b/editor/import/resource_importer_dynamic_font.cpp index 44440a92bd..2f1445af48 100644 --- a/editor/import/resource_importer_dynamic_font.cpp +++ b/editor/import/resource_importer_dynamic_font.cpp @@ -114,6 +114,7 @@ void ResourceImporterDynamicFont::get_import_options(const String &p_path, List< r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_RANGE, "1,100,1"), 8)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_RANGE, "1,250,1"), 48)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "allow_system_fallback"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "force_autohinter"), false)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), 1)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One Half of a Pixel,One Quarter of a Pixel"), 1)); @@ -150,13 +151,14 @@ Error ResourceImporterDynamicFont::import(const String &p_source_file, const Str Dictionary ot_ov = p_options["opentype_features"]; bool autohinter = p_options["force_autohinter"]; + bool allow_system_fallback = p_options["allow_system_fallback"]; int hinting = p_options["hinting"]; int subpixel_positioning = p_options["subpixel_positioning"]; real_t oversampling = p_options["oversampling"]; Array fallbacks = p_options["fallbacks"]; // Load base font data. - Vector<uint8_t> data = FileAccess::get_file_as_array(p_source_file); + Vector<uint8_t> data = FileAccess::get_file_as_bytes(p_source_file); // Create font. Ref<FontFile> font; @@ -170,6 +172,7 @@ Error ResourceImporterDynamicFont::import(const String &p_source_file, const Str font->set_opentype_feature_overrides(ot_ov); font->set_fixed_size(0); font->set_force_autohinter(autohinter); + font->set_allow_system_fallback(allow_system_fallback); font->set_subpixel_positioning((TextServer::SubpixelPositioning)subpixel_positioning); font->set_hinting((TextServer::Hinting)hinting); font->set_oversampling(oversampling); diff --git a/editor/import/resource_importer_imagefont.cpp b/editor/import/resource_importer_imagefont.cpp index eb4916663e..9c3304d946 100644 --- a/editor/import/resource_importer_imagefont.cpp +++ b/editor/import/resource_importer_imagefont.cpp @@ -121,6 +121,7 @@ Error ResourceImporterImageFont::import(const String &p_source_file, const Strin font->set_fixed_size(chr_height); font->set_subpixel_positioning(TextServer::SUBPIXEL_POSITIONING_DISABLED); font->set_force_autohinter(false); + font->set_allow_system_fallback(false); font->set_hinting(TextServer::HINTING_NONE); font->set_oversampling(1.0f); font->set_fallbacks(fallbacks); diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index a9ce6e9f88..9566c1b23c 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -2448,7 +2448,7 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p Vector<Vector<uint8_t>> mesh_lightmap_caches; { - src_lightmap_cache = FileAccess::get_file_as_array(p_source_file + ".unwrap_cache", &err); + src_lightmap_cache = FileAccess::get_file_as_bytes(p_source_file + ".unwrap_cache", &err); if (err != OK) { src_lightmap_cache.clear(); } diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 0f67663948..dbd1b12a94 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -376,11 +376,11 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) { undo_redo->commit_action(); } -void AnimationNodeBlendTreeEditor::_popup(bool p_has_input_ports, const Vector2 &p_popup_position, const Vector2 &p_node_position) { +void AnimationNodeBlendTreeEditor::_popup(bool p_has_input_ports, const Vector2 &p_node_position) { _update_options_menu(p_has_input_ports); use_position_from_popup_menu = true; position_from_popup_menu = p_node_position; - add_node->get_popup()->set_position(p_popup_position); + add_node->get_popup()->set_position(graph->get_screen_position() + graph->get_local_mouse_position()); add_node->get_popup()->reset_size(); add_node->get_popup()->popup(); } @@ -390,7 +390,7 @@ void AnimationNodeBlendTreeEditor::_popup_request(const Vector2 &p_position) { return; } - _popup(false, graph->get_screen_position() + graph->get_local_mouse_position(), p_position); + _popup(false, p_position); } void AnimationNodeBlendTreeEditor::_connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position) { @@ -401,7 +401,7 @@ void AnimationNodeBlendTreeEditor::_connection_to_empty(const String &p_from, in Ref<AnimationNode> node = blend_tree->get_node(p_from); if (node.is_valid()) { from_node = p_from; - _popup(true, p_release_position, graph->get_global_mouse_position()); + _popup(true, p_release_position); } } @@ -414,7 +414,7 @@ void AnimationNodeBlendTreeEditor::_connection_from_empty(const String &p_to, in if (node.is_valid()) { to_node = p_to; to_slot = p_to_slot; - _popup(false, p_release_position, graph->get_global_mouse_position()); + _popup(false, p_release_position); } } diff --git a/editor/plugins/animation_blend_tree_editor_plugin.h b/editor/plugins/animation_blend_tree_editor_plugin.h index fb19cce147..4b55aa9b3f 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.h +++ b/editor/plugins/animation_blend_tree_editor_plugin.h @@ -117,7 +117,7 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { void _filter_toggled(); Ref<AnimationNode> _filter_edit; - void _popup(bool p_has_input_ports, const Vector2 &p_popup_position, const Vector2 &p_node_position); + void _popup(bool p_has_input_ports, const Vector2 &p_node_position); void _popup_request(const Vector2 &p_position); void _connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position); void _connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position); diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index f8ebd377d1..5183a738ae 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -199,6 +199,7 @@ void AnimationPlayerEditor::_play_pressed() { if (current == player->get_assigned_animation()) { player->stop(); //so it won't blend with itself } + ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing."); player->play(current); } @@ -211,13 +212,12 @@ void AnimationPlayerEditor::_play_from_pressed() { if (!current.is_empty()) { float time = player->get_current_animation_position(); - if (current == player->get_assigned_animation() && player->is_playing()) { player->stop(); //so it won't blend with itself } - - player->play(current); + ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing."); player->seek(time); + player->play(current); } //unstop @@ -237,6 +237,7 @@ void AnimationPlayerEditor::_play_bw_pressed() { if (current == player->get_assigned_animation()) { player->stop(); //so it won't blend with itself } + ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing."); player->play(current, -1, -1, true); } @@ -252,9 +253,9 @@ void AnimationPlayerEditor::_play_bw_from_pressed() { if (current == player->get_assigned_animation()) { player->stop(); //so it won't blend with itself } - - player->play(current, -1, -1, true); + ERR_FAIL_COND_EDMSG(!_validate_tracks(player->get_animation(current)), "Animation tracks may have any invalid key, abort playing."); player->seek(time); + player->play(current, -1, -1, true); } //unstop @@ -1564,6 +1565,53 @@ void AnimationPlayerEditor::_pin_pressed() { SceneTreeDock::get_singleton()->get_tree_editor()->update_tree(); } +bool AnimationPlayerEditor::_validate_tracks(const Ref<Animation> p_anim) { + bool is_valid = true; + if (!p_anim.is_valid()) { + return true; // There is a problem outside of the animation track. + } + int len = p_anim->get_track_count(); + for (int i = 0; i < len; i++) { + Animation::TrackType ttype = p_anim->track_get_type(i); + if (ttype == Animation::TYPE_ROTATION_3D) { + int key_len = p_anim->track_get_key_count(i); + for (int j = 0; j < key_len; j++) { + Quaternion q; + p_anim->rotation_track_get_key(i, j, &q); + ERR_BREAK_EDMSG(!q.is_normalized(), "AnimationPlayer: '" + player->get_name() + "', Animation: '" + player->get_current_animation() + "', rotation track: '" + p_anim->track_get_path(i) + "' contains unnormalized Quaternion key."); + } + } else if (ttype == Animation::TYPE_VALUE) { + int key_len = p_anim->track_get_key_count(i); + if (key_len == 0) { + continue; + } + switch (p_anim->track_get_key_value(i, 0).get_type()) { + case Variant::QUATERNION: { + for (int j = 0; j < key_len; j++) { + Quaternion q = Quaternion(p_anim->track_get_key_value(i, j)); + if (!q.is_normalized()) { + is_valid = false; + ERR_BREAK_EDMSG(true, "AnimationPlayer: '" + player->get_name() + "', Animation: '" + player->get_current_animation() + "', value track: '" + p_anim->track_get_path(i) + "' contains unnormalized Quaternion key."); + } + } + } break; + case Variant::TRANSFORM3D: { + for (int j = 0; j < key_len; j++) { + Transform3D t = Transform3D(p_anim->track_get_key_value(i, j)); + if (!t.basis.orthonormalized().is_rotation()) { + is_valid = false; + ERR_BREAK_EDMSG(true, "AnimationPlayer: '" + player->get_name() + "', Animation: '" + player->get_current_animation() + "', value track: '" + p_anim->track_get_path(i) + "' contains corrupted basis (some axes are too close other axis or scaled by zero) Transform3D key."); + } + } + } break; + default: { + } break; + } + } + } + return is_valid; +} + void AnimationPlayerEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_animation_new"), &AnimationPlayerEditor::_animation_new); ClassDB::bind_method(D_METHOD("_animation_rename"), &AnimationPlayerEditor::_animation_rename); diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 6370b00ea8..53d460fc9e 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -212,6 +212,8 @@ class AnimationPlayerEditor : public VBoxContainer { void _start_onion_skinning(); void _stop_onion_skinning(); + bool _validate_tracks(const Ref<Animation> p_anim); + void _pin_pressed(); String _get_current() const; diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h index c5f7d2a047..586cd2bd59 100644 --- a/editor/plugins/gdextension_export_plugin.h +++ b/editor/plugins/gdextension_export_plugin.h @@ -54,60 +54,38 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p String entry_symbol = config->get_value("configuration", "entry_symbol"); - List<String> libraries; - - config->get_section_keys("libraries", &libraries); - - bool could_export = false; - for (const String &E : libraries) { - Vector<String> tags = E.split("."); - bool all_tags_met = true; - for (int i = 0; i < tags.size(); i++) { - String tag = tags[i].strip_edges(); - if (!p_features.has(tag)) { - all_tags_met = false; - break; - } - } - - if (all_tags_met) { - String library_path = config->get_value("libraries", E); - if (!library_path.begins_with("res://")) { - print_line("Skipping export of out-of-project library " + library_path); - continue; - } - add_shared_object(library_path, tags); - - if (p_features.has("iOS") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) { - String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" - "extern void add_ios_init_callback(void (*cb)());\n" - "\n" - "extern \"C\" void $ENTRY();\n" - "void $ENTRY_init() {\n" - " if (&$ENTRY) register_dynamic_symbol((char *)\"$ENTRY\", (void *)$ENTRY);\n" - "}\n" - "struct $ENTRY_struct {\n" - " $ENTRY_struct() {\n" - " add_ios_init_callback($ENTRY_init);\n" - " }\n" - "};\n" - "$ENTRY_struct $ENTRY_struct_instance;\n\n"; - additional_code = additional_code.replace("$ENTRY", entry_symbol); - add_ios_cpp_code(additional_code); - - String linker_flags = "-Wl,-U,_" + entry_symbol; - add_ios_linker_flags(linker_flags); - } - could_export = true; - break; + PackedStringArray tags; + String library_path = NativeExtension::find_extension_library( + p_path, config, [p_features](String p_feature) { return p_features.has(p_feature); }, &tags); + if (!library_path.is_empty()) { + add_shared_object(library_path, tags); + + if (p_features.has("iOS") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) { + String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" + "extern void add_ios_init_callback(void (*cb)());\n" + "\n" + "extern \"C\" void $ENTRY();\n" + "void $ENTRY_init() {\n" + " if (&$ENTRY) register_dynamic_symbol((char *)\"$ENTRY\", (void *)$ENTRY);\n" + "}\n" + "struct $ENTRY_struct {\n" + " $ENTRY_struct() {\n" + " add_ios_init_callback($ENTRY_init);\n" + " }\n" + "};\n" + "$ENTRY_struct $ENTRY_struct_instance;\n\n"; + additional_code = additional_code.replace("$ENTRY", entry_symbol); + add_ios_cpp_code(additional_code); + + String linker_flags = "-Wl,-U,_" + entry_symbol; + add_ios_linker_flags(linker_flags); } - } - if (!could_export) { - Vector<String> tags; + } else { + Vector<String> features_vector; for (const String &E : p_features) { - tags.append(E); + features_vector.append(E); } - ERR_FAIL_MSG(vformat("No suitable library found. The libraries' tags referred to an invalid feature flag. Possible feature flags for your platform: %s", p_path, String(", ").join(tags))); + ERR_FAIL_MSG(vformat("No suitable library found for GDExtension: %s. Possible feature flags for your platform: %s", p_path, String(", ").join(features_vector))); } List<String> dependencies; @@ -115,11 +93,11 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p config->get_section_keys("dependencies", &dependencies); } - for (const String &E : libraries) { - Vector<String> tags = E.split("."); + for (const String &E : dependencies) { + Vector<String> dependency_tags = E.split("."); bool all_tags_met = true; - for (int i = 0; i < tags.size(); i++) { - String tag = tags[i].strip_edges(); + for (int i = 0; i < dependency_tags.size(); i++) { + String tag = dependency_tags[i].strip_edges(); if (!p_features.has(tag)) { all_tags_met = false; break; @@ -129,13 +107,12 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p if (all_tags_met) { Dictionary dependency = config->get_value("dependencies", E); for (const Variant *key = dependency.next(nullptr); key; key = dependency.next(key)) { - String library_path = *key; + String dependency_path = *key; String target_path = dependency[*key]; - if (!library_path.begins_with("res://")) { - print_line("Skipping export of out-of-project library " + library_path); - continue; + if (dependency_path.is_relative_path()) { + dependency_path = p_path.get_base_dir().path_join(dependency_path); } - add_shared_object(library_path, tags, target_path); + add_shared_object(dependency_path, dependency_tags, target_path); } break; } diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index 0af2a13df2..c8b80db334 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -30,6 +30,7 @@ #include "node_3d_editor_gizmos.h" +#include "core/config/project_settings.h" #include "core/math/convex_hull.h" #include "core/math/geometry_2d.h" #include "core/math/geometry_3d.h" @@ -1732,6 +1733,24 @@ Camera3DGizmoPlugin::Camera3DGizmoPlugin() { create_handle_material("handles"); } +Size2i Camera3DGizmoPlugin::_get_viewport_size(Camera3D *p_camera) { + Viewport *viewport = p_camera->get_viewport(); + + Window *window = Object::cast_to<Window>(viewport); + if (window) { + return window->get_size(); + } + + SubViewport *sub_viewport = Object::cast_to<SubViewport>(viewport); + ERR_FAIL_NULL_V(sub_viewport, Size2i()); + + if (sub_viewport == EditorNode::get_singleton()->get_scene_root()) { + return Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); + } + + return sub_viewport->get_size(); +} + bool Camera3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { return Object::cast_to<Camera3D>(p_spatial) != nullptr; } @@ -1830,6 +1849,10 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Ref<Material> material = get_material("camera_material", p_gizmo); + const Size2i viewport_size = _get_viewport_size(camera); + const real_t viewport_aspect = viewport_size.x > 0 && viewport_size.y > 0 ? viewport_size.aspect() : 1.0; + const Size2 size_factor = viewport_aspect > 1.0 ? Size2(1.0, 1.0 / viewport_aspect) : Size2(viewport_aspect, 1.0); + #define ADD_TRIANGLE(m_a, m_b, m_c) \ { \ lines.push_back(m_a); \ @@ -1857,10 +1880,11 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { // The real FOV is halved for accurate representation float fov = camera->get_fov() / 2.0; - Vector3 side = Vector3(Math::sin(Math::deg_to_rad(fov)), 0, -Math::cos(Math::deg_to_rad(fov))); - Vector3 nside = side; - nside.x = -nside.x; - Vector3 up = Vector3(0, side.x, 0); + const float hsize = Math::sin(Math::deg_to_rad(fov)); + const float depth = -Math::cos(Math::deg_to_rad(fov)); + Vector3 side = Vector3(hsize * size_factor.x, 0, depth); + Vector3 nside = Vector3(-side.x, side.y, side.z); + Vector3 up = Vector3(0, hsize * size_factor.y, 0); ADD_TRIANGLE(Vector3(), side + up, side - up); ADD_TRIANGLE(Vector3(), nside + up, nside - up); @@ -1868,18 +1892,18 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { ADD_TRIANGLE(Vector3(), side - up, nside - up); handles.push_back(side); - side.x *= 0.25; - nside.x *= 0.25; - Vector3 tup(0, up.y * 3 / 2, side.z); + side.x = MIN(side.x, hsize * 0.25); + nside.x = -side.x; + Vector3 tup(0, up.y + hsize / 2, side.z); ADD_TRIANGLE(tup, side + up, nside + up); - } break; + case Camera3D::PROJECTION_ORTHOGONAL: { float size = camera->get_size(); float hsize = size * 0.5; - Vector3 right(hsize, 0, 0); - Vector3 up(0, hsize, 0); + Vector3 right(hsize * size_factor.x, 0, 0); + Vector3 up(0, hsize * size_factor.y, 0); Vector3 back(0, 0, -1.0); Vector3 front(0, 0, 0); @@ -1890,18 +1914,19 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { handles.push_back(right + back); - right.x *= 0.25; - Vector3 tup(0, up.y * 3 / 2, back.z); + right.x = MIN(right.x, hsize * 0.25); + Vector3 tup(0, up.y + hsize / 2, back.z); ADD_TRIANGLE(tup, right + up + back, -right + up + back); } break; + case Camera3D::PROJECTION_FRUSTUM: { float hsize = camera->get_size() / 2.0; Vector3 side = Vector3(hsize, 0, -camera->get_near()).normalized(); - Vector3 nside = side; - nside.x = -nside.x; - Vector3 up = Vector3(0, side.x, 0); + side.x *= size_factor.x; + Vector3 nside = Vector3(-side.x, side.y, side.z); + Vector3 up = Vector3(0, hsize * size_factor.y, 0); Vector3 offset = Vector3(camera->get_frustum_offset().x, camera->get_frustum_offset().y, 0.0); ADD_TRIANGLE(Vector3(), side + up + offset, side - up + offset); @@ -1909,11 +1934,11 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { ADD_TRIANGLE(Vector3(), side + up + offset, nside + up + offset); ADD_TRIANGLE(Vector3(), side - up + offset, nside - up + offset); - side.x *= 0.25; - nside.x *= 0.25; - Vector3 tup(0, up.y * 3 / 2, side.z); + side.x = MIN(side.x, hsize * 0.25); + nside.x = -side.x; + Vector3 tup(0, up.y + hsize / 2, side.z); ADD_TRIANGLE(tup + offset, side + up + offset, nside + up + offset); - } + } break; } #undef ADD_TRIANGLE @@ -1921,7 +1946,10 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_lines(lines, material); p_gizmo->add_collision_segments(lines); - p_gizmo->add_handles(handles, get_material("handles")); + + if (!handles.is_empty()) { + p_gizmo->add_handles(handles, get_material("handles")); + } } ////// diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h index d7e3e03f61..60d44ad787 100644 --- a/editor/plugins/node_3d_editor_gizmos.h +++ b/editor/plugins/node_3d_editor_gizmos.h @@ -264,6 +264,9 @@ public: class Camera3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(Camera3DGizmoPlugin, EditorNode3DGizmoPlugin); +private: + static Size2i _get_viewport_size(Camera3D *p_camera); + public: bool has_gizmo(Node3D *p_spatial) override; String get_gizmo_name() const override; diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 4194fd831b..112a3fa51b 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -92,6 +92,9 @@ void ViewportNavigationControl::_notification(int p_what) { if (!is_connected("mouse_exited", callable_mp(this, &ViewportNavigationControl::_on_mouse_exited))) { connect("mouse_exited", callable_mp(this, &ViewportNavigationControl::_on_mouse_exited)); } + if (!is_connected("mouse_entered", callable_mp(this, &ViewportNavigationControl::_on_mouse_entered))) { + connect("mouse_entered", callable_mp(this, &ViewportNavigationControl::_on_mouse_entered)); + } } break; case NOTIFICATION_DRAW: { @@ -112,7 +115,7 @@ void ViewportNavigationControl::_draw() { float radius = get_size().x / 2.0; const bool focused = focused_index != -1; - draw_circle(center, radius, Color(0.5, 0.5, 0.5, focused ? 0.25 : 0.05)); + draw_circle(center, radius, Color(0.5, 0.5, 0.5, focused || hovered ? 0.35 : 0.15)); const Color c = focused ? Color(0.9, 0.9, 0.9, 0.9) : Color(0.5, 0.5, 0.5, 0.25); @@ -123,6 +126,9 @@ void ViewportNavigationControl::_draw() { } void ViewportNavigationControl::_process_click(int p_index, Vector2 p_position, bool p_pressed) { + hovered = false; + queue_redraw(); + if (focused_index != -1 && focused_index != p_index) { return; } @@ -233,7 +239,13 @@ void ViewportNavigationControl::_update_navigation() { } } +void ViewportNavigationControl::_on_mouse_entered() { + hovered = true; + queue_redraw(); +} + void ViewportNavigationControl::_on_mouse_exited() { + hovered = false; queue_redraw(); } @@ -5133,7 +5145,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p // Prevent visible spacing between frame time labels. top_right_vbox->add_theme_constant_override("separation", 0); - const int navigation_control_size = 200; + const int navigation_control_size = 150; position_control = memnew(ViewportNavigationControl); position_control->set_navigation_mode(Node3DEditorViewport::NAVIGATION_MOVE); @@ -7465,6 +7477,8 @@ void Node3DEditor::_notification(int p_what) { sun_state->set_custom_minimum_size(sun_vb->get_combined_minimum_size()); environ_state->set_custom_minimum_size(environ_vb->get_combined_minimum_size()); + + EditorNode::get_singleton()->connect("project_settings_changed", callable_mp(this, &Node3DEditor::update_all_gizmos).bind(Variant())); } break; case NOTIFICATION_ENTER_TREE: { @@ -8408,7 +8422,7 @@ Node3DEditor::Node3DEditor() { EDITOR_DEF("editors/3d/manipulator_gizmo_opacity", 0.9); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::FLOAT, "editors/3d/manipulator_gizmo_opacity", PROPERTY_HINT_RANGE, "0,1,0.01")); EDITOR_DEF_RST("editors/3d/navigation/show_viewport_rotation_gizmo", true); - EDITOR_DEF_RST("editors/3d/navigation/show_viewport_navigation_gizmo", DisplayServer::get_singleton()->screen_is_touchscreen()); + EDITOR_DEF_RST("editors/3d/navigation/show_viewport_navigation_gizmo", DisplayServer::get_singleton()->is_touchscreen_available()); current_hover_gizmo_handle = -1; current_hover_gizmo_handle_secondary = false; diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 04fc030f2b..ed555d86c3 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -930,6 +930,7 @@ class ViewportNavigationControl : public Control { Node3DEditorViewport *viewport = nullptr; Vector2i focused_mouse_start; Vector2 focused_pos; + bool hovered = false; int focused_index = -1; Node3DEditorViewport::NavigationMode nav_mode = Node3DEditorViewport::NavigationMode::NAVIGATION_NONE; @@ -939,6 +940,7 @@ protected: void _notification(int p_what); virtual void gui_input(const Ref<InputEvent> &p_event) override; void _draw(); + void _on_mouse_entered(); void _on_mouse_exited(); void _process_click(int p_index, Vector2 p_position, bool p_pressed); void _process_drag(int p_index, Vector2 p_position, Vector2 p_relative_position); diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index 5a7b0321b7..63ca78d6c0 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -240,38 +240,63 @@ void Path3DGizmo::redraw() { return; } - Vector<Vector3> v3a = c->tessellate(); - //Vector<Vector3> v3a=c->get_baked_points(); + real_t interval = 0.1; + const real_t length = c->get_baked_length(); - int v3s = v3a.size(); - if (v3s == 0) { - return; - } - Vector<Vector3> v3p; - const Vector3 *r = v3a.ptr(); - - // BUG: the following won't work when v3s, avoid drawing as a temporary workaround. - for (int i = 0; i < v3s - 1; i++) { - v3p.push_back(r[i]); - v3p.push_back(r[i + 1]); - //v3p.push_back(r[i]); - //v3p.push_back(r[i]+Vector3(0,0.2,0)); - } + // 1. Draw curve and bones. + if (length > CMP_EPSILON) { + const int sample_count = int(length / interval) + 2; + interval = length / (sample_count - 1); // Recalculate real interval length. + + Vector<Transform3D> frames; + frames.resize(sample_count); + + { + Transform3D *w = frames.ptrw(); + + for (int i = 0; i < sample_count; i++) { + w[i] = c->sample_baked_with_rotation(i * interval, true, true); + } + } + + const Transform3D *r = frames.ptr(); + Vector<Vector3> v3p; + for (int i = 0; i < sample_count - 1; i++) { + const Vector3 p1 = r[i].origin; + const Vector3 p2 = r[i + 1].origin; + const Vector3 side = r[i].basis.get_column(0); + const Vector3 up = r[i].basis.get_column(1); + const Vector3 forward = r[i].basis.get_column(2); + + // Curve segment. + v3p.push_back(p1); + v3p.push_back(p2); + + // Fish Bone. + v3p.push_back(p1); + v3p.push_back(p1 + (side - forward) * 0.06); + + v3p.push_back(p1); + v3p.push_back(p1 + (-side - forward) * 0.06); + + v3p.push_back(p1); + v3p.push_back(p1 + up * 0.03); + } - if (v3p.size() > 1) { add_lines(v3p, path_material); add_collision_segments(v3p); } + // 2. Draw handles. if (Path3DEditorPlugin::singleton->get_edited_path() == path) { - v3p.clear(); + Vector<Vector3> v3p; Vector<Vector3> handle_points; Vector<Vector3> sec_handle_points; for (int i = 0; i < c->get_point_count(); i++) { Vector3 p = c->get_point_position(i); handle_points.push_back(p); - // push Out points first so they get selected if the In and Out points are on top of each other. + // Push out points first so they get selected if the In and Out points are on top of each other. if (i < c->get_point_count() - 1) { v3p.push_back(p); v3p.push_back(p + c->get_point_out(i)); diff --git a/editor/plugins/tiles/atlas_merging_dialog.cpp b/editor/plugins/tiles/atlas_merging_dialog.cpp index e266d26b73..f892f3637d 100644 --- a/editor/plugins/tiles/atlas_merging_dialog.cpp +++ b/editor/plugins/tiles/atlas_merging_dialog.cpp @@ -236,7 +236,7 @@ void AtlasMergingDialog::update_tile_set(Ref<TileSet> p_tile_set) { if (atlas_source.is_valid()) { Ref<Texture2D> texture = atlas_source->get_texture(); if (texture.is_valid()) { - String item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id); + String item_text = vformat(TTR("%s (ID: %d)"), texture->get_path().get_file(), source_id); atlas_merging_atlases_list->add_item(item_text, texture); atlas_merging_atlases_list->set_item_metadata(-1, source_id); } diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index e622a0817a..29578aa560 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -156,7 +156,7 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { // Common to all type of sources. if (!source->get_name().is_empty()) { - item_text = vformat(TTR("%s (id:%d)"), source->get_name(), source_id); + item_text = vformat(TTR("%s (ID: %d)"), source->get_name(), source_id); } // Atlas source. @@ -165,7 +165,7 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { texture = atlas_source->get_texture(); if (item_text.is_empty()) { if (texture.is_valid()) { - item_text = vformat("%s (ID: %d)", texture->get_path().get_file(), source_id); + item_text = vformat(TTR("%s (ID: %d)"), texture->get_path().get_file(), source_id); } else { item_text = vformat(TTR("No Texture Atlas Source (ID: %d)"), source_id); } @@ -1164,7 +1164,7 @@ HashMap<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vecto } // Get surrounding tiles (handles different tile shapes). - TypedArray<Vector2i> around = tile_map->get_surrounding_tiles(coords); + TypedArray<Vector2i> around = tile_map->get_surrounding_cells(coords); for (int i = 0; i < around.size(); i++) { to_check.push_back(around[i]); } @@ -2547,7 +2547,7 @@ RBSet<Vector2i> TileMapEditorTerrainsPlugin::_get_cells_for_bucket_fill(Vector2i output.insert(coords); // Get surrounding tiles (handles different tile shapes). - TypedArray<Vector2i> around = tile_map->get_surrounding_tiles(coords); + TypedArray<Vector2i> around = tile_map->get_surrounding_cells(coords); for (int i = 0; i < around.size(); i++) { to_check.push_back(around[i]); } diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp index 7058b28e68..b31fb1aa58 100644 --- a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp @@ -36,7 +36,7 @@ #include "editor/editor_undo_redo_manager.h" #include "scene/gui/separator.h" -void TileProxiesManagerDialog::_right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list, MouseButton p_mouse_button_index) { +void TileProxiesManagerDialog::_right_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index, Object *p_item_list) { if (p_mouse_button_index != MouseButton::RIGHT) { return; } @@ -77,7 +77,7 @@ void TileProxiesManagerDialog::_delete_selected_bindings() { Vector<int> alternative_level_selected = alternative_level_list->get_selected_items(); for (int i = 0; i < alternative_level_selected.size(); i++) { Array key = alternative_level_list->get_item_metadata(alternative_level_selected[i]); - Array val = tile_set->get_coords_level_tile_proxy(key[0], key[1]); + Array val = tile_set->get_alternative_level_tile_proxy(key[0], key[1], key[2]); undo_redo->add_do_method(*tile_set, "remove_alternative_level_tile_proxy", key[0], key[1], key[2]); undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", key[0], key[1], key[2], val[0], val[1], val[2]); } diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.h b/editor/plugins/tiles/tile_proxies_manager_dialog.h index e2363eb809..09c8068336 100644 --- a/editor/plugins/tiles/tile_proxies_manager_dialog.h +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.h @@ -61,7 +61,7 @@ private: EditorPropertyInteger *alternative_to_property_editor = nullptr; PopupMenu *popup_menu = nullptr; - void _right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list, MouseButton p_mouse_button_index); + void _right_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index, Object *p_item_list); void _menu_id_pressed(int p_id); void _delete_selected_bindings(); void _update_lists(); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index ae7570e161..7ed84423bc 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -2345,6 +2345,15 @@ void TileSetAtlasSourceEditor::_notification(int p_what) { tile_set_changed_needs_update = false; } } break; + + case NOTIFICATION_EXIT_TREE: { + for (KeyValue<String, TileDataEditor *> &E : tile_data_editors) { + Control *toolbar = E.value->get_toolbar(); + if (toolbar->get_parent() == tool_settings_tile_data_toolbar_container) { + tool_settings_tile_data_toolbar_container->remove_child(toolbar); + } + } + } break; } } diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index b24c5059b0..dbecf52398 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -151,7 +151,7 @@ void TileSetEditor::_update_sources_list(int force_selected_id) { // Common to all type of sources. if (!source->get_name().is_empty()) { - item_text = vformat(TTR("%s (id:%d)"), source->get_name(), source_id); + item_text = vformat(TTR("%s (ID: %d)"), source->get_name(), source_id); } // Atlas source. @@ -160,7 +160,7 @@ void TileSetEditor::_update_sources_list(int force_selected_id) { texture = atlas_source->get_texture(); if (item_text.is_empty()) { if (texture.is_valid()) { - item_text = vformat("%s (ID: %d)", texture->get_path().get_file(), source_id); + item_text = vformat(TTR("%s (ID: %d)"), texture->get_path().get_file(), source_id); } else { item_text = vformat(TTR("No Texture Atlas Source (ID: %d)"), source_id); } diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp index 86aa897c78..369a59c443 100644 --- a/editor/plugins/version_control_editor_plugin.cpp +++ b/editor/plugins/version_control_editor_plugin.cpp @@ -988,7 +988,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { metadata_dialog->set_title(TTR("Create Version Control Metadata")); metadata_dialog->set_min_size(Size2(200, 40)); metadata_dialog->get_ok_button()->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_create_vcs_metadata_files)); - version_control_actions->add_child(metadata_dialog); + add_child(metadata_dialog); VBoxContainer *metadata_vb = memnew(VBoxContainer); metadata_dialog->add_child(metadata_vb); @@ -1017,7 +1017,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { set_up_dialog->set_min_size(Size2(600, 100)); set_up_dialog->add_cancel_button("Cancel"); set_up_dialog->set_hide_on_ok(true); - version_control_actions->add_child(set_up_dialog); + add_child(set_up_dialog); Button *set_up_apply_button = set_up_dialog->get_ok_button(); set_up_apply_button->set_text(TTR("Apply")); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index f32e0bdfa2..9990d5c06f 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -5209,6 +5209,7 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("NodePositionWorld", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "node_position_world", "NODE_POSITION_WORLD"), { "node_position_world" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("CameraPositionWorld", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "camera_position_world", "CAMERA_POSITION_WORLD"), { "camera_position_world" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("CameraDirectionWorld", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "camera_direction_world", "CAMERA_DIRECTION_WORLD"), { "camera_direction_world" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("CameraVisibleLayers", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "camera_visible_layers", "CAMERA_VISIBLE_LAYERS"), { "camera_visible_layers" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("NodePositionView", "Input/Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "node_position_view", "NODE_POSITION_VIEW"), { "node_position_view" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Binormal", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "binormal", "BINORMAL"), { "binormal" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); @@ -5228,6 +5229,7 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("NodePositionWorld", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "node_position_world", "NODE_POSITION_WORLD"), { "node_position_world" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("CameraPositionWorld", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "camera_position_world", "CAMERA_POSITION_WORLD"), { "camera_position_world" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("CameraDirectionWorld", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "camera_direction_world", "CAMERA_DIRECTION_WORLD"), { "camera_direction_world" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("CameraVisibleLayers", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "camera_visible_layers", "CAMERA_VISIBLE_LAYERS"), { "camera_visible_layers" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("NodePositionView", "Input/Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "node_position_view", "NODE_POSITION_VIEW"), { "node_position_view" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Albedo", "Input/Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "albedo", "ALBEDO"), { "albedo" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 28111bed58..1e917e6b3d 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -274,10 +274,12 @@ void ProjectSettingsEditor::_add_feature_overrides() { presets.insert("s3tc"); presets.insert("etc"); presets.insert("etc2"); + presets.insert("editor"); + presets.insert("template_debug"); + presets.insert("template_release"); presets.insert("debug"); presets.insert("release"); - presets.insert("editor"); - presets.insert("standalone"); + presets.insert("template"); presets.insert("32"); presets.insert("64"); presets.insert("movie"); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 0ebd8e610a..96688a3614 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -2453,9 +2453,12 @@ void SceneTreeDock::_new_scene_from(String p_file) { Node *copy = base->duplicate_from_editor(duplimap); if (copy) { + // Handle Unique Nodes. for (int i = 0; i < copy->get_child_count(false); i++) { _set_node_owner_recursive(copy->get_child(i, false), copy); } + // Root node cannot ever be unique name in its own Scene! + copy->set_unique_name_in_owner(false); Ref<PackedScene> sdata = memnew(PackedScene); Error err = sdata->pack(copy); diff --git a/editor/shader_globals_editor.cpp b/editor/shader_globals_editor.cpp index 22a1d49422..b778262fed 100644 --- a/editor/shader_globals_editor.cpp +++ b/editor/shader_globals_editor.cpp @@ -69,28 +69,12 @@ static const char *global_var_type_names[RS::GLOBAL_VAR_TYPE_MAX] = { class ShaderGlobalsEditorInterface : public Object { GDCLASS(ShaderGlobalsEditorInterface, Object) - void _var_changed() { - emit_signal(SNAME("var_changed")); - } - -protected: - static void _bind_methods() { - ClassDB::bind_method("_var_changed", &ShaderGlobalsEditorInterface::_var_changed); - ADD_SIGNAL(MethodInfo("var_changed")); - } - - bool _set(const StringName &p_name, const Variant &p_value) { - Variant existing = RS::get_singleton()->global_shader_parameter_get(p_name); - - if (existing.get_type() == Variant::NIL) { - return false; - } - + void _set_var(const StringName &p_name, const Variant &p_value, const Variant &p_prev_value) { Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo(); undo_redo->create_action(TTR("Set Shader Global Variable")); undo_redo->add_do_method(RS::get_singleton(), "global_shader_parameter_set", p_name, p_value); - undo_redo->add_undo_method(RS::get_singleton(), "global_shader_parameter_set", p_name, existing); + undo_redo->add_undo_method(RS::get_singleton(), "global_shader_parameter_set", p_name, p_prev_value); RS::GlobalShaderParameterType type = RS::get_singleton()->global_shader_parameter_get_type(p_name); Dictionary gv; gv["type"] = global_var_type_names[type]; @@ -111,8 +95,29 @@ protected: undo_redo->add_do_method(this, "_var_changed"); undo_redo->add_undo_method(this, "_var_changed"); block_update = true; - undo_redo->commit_action(false); + undo_redo->commit_action(); block_update = false; + } + + void _var_changed() { + emit_signal(SNAME("var_changed")); + } + +protected: + static void _bind_methods() { + ClassDB::bind_method("_set_var", &ShaderGlobalsEditorInterface::_set_var); + ClassDB::bind_method("_var_changed", &ShaderGlobalsEditorInterface::_var_changed); + ADD_SIGNAL(MethodInfo("var_changed")); + } + + bool _set(const StringName &p_name, const Variant &p_value) { + Variant existing = RS::get_singleton()->global_shader_parameter_get(p_name); + + if (existing.get_type() == Variant::NIL) { + return false; + } + + call_deferred("_set_var", p_name, p_value, existing); return true; } diff --git a/main/main.cpp b/main/main.cpp index 2d0843a331..83703c55c2 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -311,29 +311,29 @@ void Main::print_help(const char *p_binary) { OS::get_singleton()->print("\n"); OS::get_singleton()->print("General options:\n"); - OS::get_singleton()->print(" -h, --help Display this help message.\n"); - OS::get_singleton()->print(" --version Display the version string.\n"); - OS::get_singleton()->print(" -v, --verbose Use verbose stdout mode.\n"); - OS::get_singleton()->print(" -q, --quiet Quiet mode, silences stdout messages. Errors are still displayed.\n"); + OS::get_singleton()->print(" -h, --help Display this help message.\n"); + OS::get_singleton()->print(" --version Display the version string.\n"); + OS::get_singleton()->print(" -v, --verbose Use verbose stdout mode.\n"); + OS::get_singleton()->print(" -q, --quiet Quiet mode, silences stdout messages. Errors are still displayed.\n"); OS::get_singleton()->print("\n"); OS::get_singleton()->print("Run options:\n"); - OS::get_singleton()->print(" --, ++ Separator for user-provided arguments. Following arguments are not used by the engine, but can be read from `OS.get_cmdline_user_args()`.\n"); + OS::get_singleton()->print(" --, ++ Separator for user-provided arguments. Following arguments are not used by the engine, but can be read from `OS.get_cmdline_user_args()`.\n"); #ifdef TOOLS_ENABLED - OS::get_singleton()->print(" -e, --editor Start the editor instead of running the scene.\n"); - OS::get_singleton()->print(" -p, --project-manager Start the project manager, even if a project is auto-detected.\n"); - OS::get_singleton()->print(" --debug-server <uri> Start the editor debug server (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007)\n"); + OS::get_singleton()->print(" -e, --editor Start the editor instead of running the scene.\n"); + OS::get_singleton()->print(" -p, --project-manager Start the project manager, even if a project is auto-detected.\n"); + OS::get_singleton()->print(" --debug-server <uri> Start the editor debug server (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007)\n"); #endif - OS::get_singleton()->print(" --quit Quit after the first iteration.\n"); - OS::get_singleton()->print(" -l, --language <locale> Use a specific locale (<locale> being a two-letter code).\n"); - OS::get_singleton()->print(" --path <directory> Path to a project (<directory> must contain a 'project.godot' file).\n"); - OS::get_singleton()->print(" -u, --upwards Scan folders upwards for project.godot file.\n"); - OS::get_singleton()->print(" --main-pack <file> Path to a pack (.pck) file to load.\n"); - OS::get_singleton()->print(" --render-thread <mode> Render thread mode ('unsafe', 'safe', 'separate').\n"); - OS::get_singleton()->print(" --remote-fs <address> Remote filesystem (<host/IP>[:<port>] address).\n"); - OS::get_singleton()->print(" --remote-fs-password <password> Password for remote filesystem.\n"); - - OS::get_singleton()->print(" --audio-driver <driver> Audio driver ["); + OS::get_singleton()->print(" --quit Quit after the first iteration.\n"); + OS::get_singleton()->print(" -l, --language <locale> Use a specific locale (<locale> being a two-letter code).\n"); + OS::get_singleton()->print(" --path <directory> Path to a project (<directory> must contain a 'project.godot' file).\n"); + OS::get_singleton()->print(" -u, --upwards Scan folders upwards for project.godot file.\n"); + OS::get_singleton()->print(" --main-pack <file> Path to a pack (.pck) file to load.\n"); + OS::get_singleton()->print(" --render-thread <mode> Render thread mode ['unsafe', 'safe', 'separate'].\n"); + OS::get_singleton()->print(" --remote-fs <address> Remote filesystem (<host/IP>[:<port>] address).\n"); + OS::get_singleton()->print(" --remote-fs-password <password> Password for remote filesystem.\n"); + + OS::get_singleton()->print(" --audio-driver <driver> Audio driver ["); for (int i = 0; i < AudioDriverManager::get_driver_count(); i++) { if (i > 0) { OS::get_singleton()->print(", "); @@ -342,7 +342,7 @@ void Main::print_help(const char *p_binary) { } OS::get_singleton()->print("].\n"); - OS::get_singleton()->print(" --display-driver <driver> Display driver (and rendering driver) ["); + OS::get_singleton()->print(" --display-driver <driver> Display driver (and rendering driver) ["); for (int i = 0; i < DisplayServer::get_create_function_count(); i++) { if (i > 0) { OS::get_singleton()->print(", "); @@ -359,72 +359,76 @@ void Main::print_help(const char *p_binary) { } OS::get_singleton()->print("].\n"); - OS::get_singleton()->print(" --rendering-method <renderer> Renderer name. Requires driver support.\n"); - OS::get_singleton()->print(" --rendering-driver <driver> Rendering driver (depends on display driver).\n"); - OS::get_singleton()->print(" --gpu-index <device_index> Use a specific GPU (run with --verbose to get available device list).\n"); - OS::get_singleton()->print(" --text-driver <driver> Text driver (Fonts, BiDi, shaping)\n"); - OS::get_singleton()->print(" --tablet-driver <driver> Pen tablet input driver.\n"); - OS::get_singleton()->print(" --headless Enable headless mode (--display-driver headless --audio-driver Dummy). Useful for servers and with --script.\n"); - OS::get_singleton()->print(" --write-movie <file> Run the engine in a way that a movie is written (by default .avi MJPEG). Fixed FPS is forced when enabled, but can be used to change movie FPS. Disabling vsync can speed up movie writing but makes interaction more difficult.\n"); - OS::get_singleton()->print(" --disable-vsync Force disabling of vsync. Run the engine in a way that a movie is written (by default .avi MJPEG). Fixed FPS is forced when enabled, but can be used to change movie FPS.\n"); + OS::get_singleton()->print(" --rendering-method <renderer> Renderer name. Requires driver support.\n"); + OS::get_singleton()->print(" --rendering-driver <driver> Rendering driver (depends on display driver).\n"); + OS::get_singleton()->print(" --gpu-index <device_index> Use a specific GPU (run with --verbose to get available device list).\n"); + OS::get_singleton()->print(" --text-driver <driver> Text driver (Fonts, BiDi, shaping).\n"); + OS::get_singleton()->print(" --tablet-driver <driver> Pen tablet input driver.\n"); + OS::get_singleton()->print(" --headless Enable headless mode (--display-driver headless --audio-driver Dummy). Useful for servers and with --script.\n"); + OS::get_singleton()->print(" --write-movie <file> Writes a video to the specified path (usually with .avi or .png extension).\n"); + OS::get_singleton()->print(" --fixed-fps is forced when enabled, but it can be used to change movie FPS.\n"); + OS::get_singleton()->print(" --disable-vsync can speed up movie writing but makes interaction more difficult.\n"); OS::get_singleton()->print("\n"); OS::get_singleton()->print("Display options:\n"); - OS::get_singleton()->print(" -f, --fullscreen Request fullscreen mode.\n"); - OS::get_singleton()->print(" -m, --maximized Request a maximized window.\n"); - OS::get_singleton()->print(" -w, --windowed Request windowed mode.\n"); - OS::get_singleton()->print(" -t, --always-on-top Request an always-on-top window.\n"); - OS::get_singleton()->print(" --resolution <W>x<H> Request window resolution.\n"); - OS::get_singleton()->print(" --position <X>,<Y> Request window position.\n"); - OS::get_singleton()->print(" --single-window Use a single window (no separate subwindows).\n"); - OS::get_singleton()->print(" --xr-mode <mode> Select XR mode (default/off/on).\n"); + OS::get_singleton()->print(" -f, --fullscreen Request fullscreen mode.\n"); + OS::get_singleton()->print(" -m, --maximized Request a maximized window.\n"); + OS::get_singleton()->print(" -w, --windowed Request windowed mode.\n"); + OS::get_singleton()->print(" -t, --always-on-top Request an always-on-top window.\n"); + OS::get_singleton()->print(" --resolution <W>x<H> Request window resolution.\n"); + OS::get_singleton()->print(" --position <X>,<Y> Request window position.\n"); + OS::get_singleton()->print(" --single-window Use a single window (no separate subwindows).\n"); + OS::get_singleton()->print(" --xr-mode <mode> Select XR (Extended Reality) mode ['default', 'off', 'on'].\n"); OS::get_singleton()->print("\n"); OS::get_singleton()->print("Debug options:\n"); - OS::get_singleton()->print(" -d, --debug Debug (local stdout debugger).\n"); - OS::get_singleton()->print(" -b, --breakpoints Breakpoint list as source::line comma-separated pairs, no spaces (use %%20 instead).\n"); - OS::get_singleton()->print(" --profiling Enable profiling in the script debugger.\n"); - OS::get_singleton()->print(" --gpu-profile Show a GPU profile of the tasks that took the most time during frame rendering.\n"); - OS::get_singleton()->print(" --gpu-validation Enable graphics API validation layers for debugging.\n"); + OS::get_singleton()->print(" -d, --debug Debug (local stdout debugger).\n"); + OS::get_singleton()->print(" -b, --breakpoints Breakpoint list as source::line comma-separated pairs, no spaces (use %%20 instead).\n"); + OS::get_singleton()->print(" --profiling Enable profiling in the script debugger.\n"); + OS::get_singleton()->print(" --gpu-profile Show a GPU profile of the tasks that took the most time during frame rendering.\n"); + OS::get_singleton()->print(" --gpu-validation Enable graphics API validation layers for debugging.\n"); #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"); + 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(" --remote-debug <uri> Remote debug (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007).\n"); + OS::get_singleton()->print(" --remote-debug <uri> Remote debug (<protocol>://<host/IP>[:<port>], e.g. tcp://127.0.0.1:6007).\n"); #if defined(DEBUG_ENABLED) - OS::get_singleton()->print(" --debug-collisions Show collision shapes when running the scene.\n"); - OS::get_singleton()->print(" --debug-paths Show path lines when running the scene.\n"); - OS::get_singleton()->print(" --debug-navigation Show navigation polygons when running the scene.\n"); - OS::get_singleton()->print(" --debug-stringnames Print all StringName allocations to stdout when the engine quits.\n"); + OS::get_singleton()->print(" --debug-collisions Show collision shapes when running the scene.\n"); + OS::get_singleton()->print(" --debug-paths Show path lines when running the scene.\n"); + OS::get_singleton()->print(" --debug-navigation Show navigation polygons when running the scene.\n"); + OS::get_singleton()->print(" --debug-stringnames Print all StringName allocations to stdout when the engine quits.\n"); #endif - OS::get_singleton()->print(" --frame-delay <ms> Simulate high CPU load (delay each frame by <ms> milliseconds).\n"); - OS::get_singleton()->print(" --time-scale <scale> Force time scale (higher values are faster, 1.0 is normal speed).\n"); - OS::get_singleton()->print(" --disable-render-loop Disable render loop so rendering only occurs when called explicitly from script.\n"); - OS::get_singleton()->print(" --disable-crash-handler Disable crash handler when supported by the platform code.\n"); - OS::get_singleton()->print(" --fixed-fps <fps> Force a fixed number of frames per second. This setting disables real-time synchronization.\n"); - OS::get_singleton()->print(" --print-fps Print the frames per second to the stdout.\n"); + OS::get_singleton()->print(" --frame-delay <ms> Simulate high CPU load (delay each frame by <ms> milliseconds).\n"); + OS::get_singleton()->print(" --time-scale <scale> Force time scale (higher values are faster, 1.0 is normal speed).\n"); + OS::get_singleton()->print(" --disable-vsync Forces disabling of vertical synchronization, even if enabled in the project settings. Does not override driver-level V-Sync enforcement.\n"); + OS::get_singleton()->print(" --disable-render-loop Disable render loop so rendering only occurs when called explicitly from script.\n"); + OS::get_singleton()->print(" --disable-crash-handler Disable crash handler when supported by the platform code.\n"); + OS::get_singleton()->print(" --fixed-fps <fps> Force a fixed number of frames per second. This setting disables real-time synchronization.\n"); + OS::get_singleton()->print(" --print-fps Print the frames per second to the stdout.\n"); OS::get_singleton()->print("\n"); OS::get_singleton()->print("Standalone tools:\n"); - OS::get_singleton()->print(" -s, --script <script> Run a script.\n"); - OS::get_singleton()->print(" --check-only Only parse for errors and quit (use with --script).\n"); + OS::get_singleton()->print(" -s, --script <script> Run a script.\n"); + OS::get_singleton()->print(" --check-only Only parse for errors and quit (use with --script).\n"); #ifdef TOOLS_ENABLED - OS::get_singleton()->print(" --export-release <preset> <path> Export the project in release mode using the given preset and output path. The preset name should match one defined in export_presets.cfg.\n"); - OS::get_singleton()->print(" <path> should be absolute or relative to the project directory, and include the filename for the binary (e.g. 'builds/game.exe').\n"); - OS::get_singleton()->print(" The target directory must exist.\n"); - OS::get_singleton()->print(" --export-debug <preset> <path> Export the project in debug mode using the given preset and output path. The preset name should match one defined in export_presets.cfg.\n"); - OS::get_singleton()->print(" --export-pack <preset> <path> Export the project data only using the given preset and output path. The <path> extension determines whether it will be in PCK or ZIP format.\n"); - OS::get_singleton()->print(" --convert-3to4 [<max_file_kb>] [<max_line_size>] Converts project from Godot 3.x to Godot 4.x.\n"); - OS::get_singleton()->print(" --validate-conversion-3to4 [<max_file_kb>] [<max_line_size>] Shows what elements will be renamed when converting project from Godot 3.x to Godot 4.x.\n"); - OS::get_singleton()->print(" --doctool [<path>] Dump the engine API reference to the given <path> (defaults to current dir) in XML format, merging if existing files are found.\n"); - OS::get_singleton()->print(" --no-docbase Disallow dumping the base types (used with --doctool).\n"); - OS::get_singleton()->print(" --build-solutions Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n"); - OS::get_singleton()->print(" --dump-gdextension-interface Generate GDExtension header file 'gdnative_interface.h' in the current folder. This file is the base file required to implement a GDExtension.\n"); - OS::get_singleton()->print(" --dump-extension-api Generate JSON dump of the Godot API for GDExtension bindings named 'extension_api.json' in the current folder.\n"); - OS::get_singleton()->print(" --startup-benchmark Benchmark the startup time and print it to console.\n"); - OS::get_singleton()->print(" --startup-benchmark-file <path> Benchmark the startup time and save it to a given file in JSON format.\n"); + OS::get_singleton()->print(" --export-release <preset> <path> Export the project in release mode using the given preset and output path. The preset name should match one defined in export_presets.cfg.\n"); + OS::get_singleton()->print(" <path> should be absolute or relative to the project directory, and include the filename for the binary (e.g. 'builds/game.exe').\n"); + OS::get_singleton()->print(" The target directory must exist.\n"); + OS::get_singleton()->print(" --export-debug <preset> <path> Export the project in debug mode using the given preset and output path. See --export-release description for other considerations.\n"); + OS::get_singleton()->print(" --export-pack <preset> <path> Export the project data only using the given preset and output path. The <path> extension determines whether it will be in PCK or ZIP format.\n"); + OS::get_singleton()->print(" --convert-3to4 [<max_file_kb>] [<max_line_size>]\n"); + OS::get_singleton()->print(" Converts project from Godot 3.x to Godot 4.x.\n"); + OS::get_singleton()->print(" --validate-conversion-3to4 [<max_file_kb>] [<max_line_size>]\n"); + OS::get_singleton()->print(" Shows what elements will be renamed when converting project from Godot 3.x to Godot 4.x.\n"); + OS::get_singleton()->print(" --doctool [<path>] Dump the engine API reference to the given <path> (defaults to current dir) in XML format, merging if existing files are found.\n"); + OS::get_singleton()->print(" --no-docbase Disallow dumping the base types (used with --doctool).\n"); + OS::get_singleton()->print(" --build-solutions Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n"); + OS::get_singleton()->print(" --dump-gdextension-interface Generate GDExtension header file 'gdnative_interface.h' in the current folder. This file is the base file required to implement a GDExtension.\n"); + OS::get_singleton()->print(" --dump-extension-api Generate JSON dump of the Godot API for GDExtension bindings named 'extension_api.json' in the current folder.\n"); + OS::get_singleton()->print(" --startup-benchmark Benchmark the startup time and print it to console.\n"); + OS::get_singleton()->print(" --startup-benchmark-file <path> Benchmark the startup time and save it to a given file in JSON format.\n"); #ifdef TESTS_ENABLED - OS::get_singleton()->print(" --test [--help] Run unit tests. Use --test --help for more information.\n"); + OS::get_singleton()->print(" --test [--help] Run unit tests. Use --test --help for more information.\n"); #endif #endif OS::get_singleton()->print("\n"); @@ -533,6 +537,10 @@ Error Main::test_setup() { void Main::test_cleanup() { ERR_FAIL_COND(!_start_success); + for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) { + TextServerManager::get_singleton()->get_interface(i)->cleanup(); + } + EngineDebugger::deinitialize(); ResourceLoader::remove_custom_loaders(); @@ -1854,6 +1862,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph GLOBAL_DEF_BASIC("xr/openxr/reference_space", "1"); ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/reference_space", PropertyInfo(Variant::INT, "xr/openxr/reference_space", PROPERTY_HINT_ENUM, "Local,Stage")); + GLOBAL_DEF_BASIC("xr/openxr/submit_depth_buffer", false); + #ifdef TOOLS_ENABLED // Disabled for now, using XR inside of the editor we'll be working on during the coming months. @@ -2225,13 +2235,7 @@ Error Main::setup2(Thread::ID p_main_tid_override) { if (bool(GLOBAL_DEF("input_devices/pointing/emulate_touch_from_mouse", false)) && !(editor || project_manager)) { - bool found_touchscreen = false; - for (int i = 0; i < DisplayServer::get_singleton()->get_screen_count(); i++) { - if (DisplayServer::get_singleton()->screen_is_touchscreen(i)) { - found_touchscreen = true; - } - } - if (!found_touchscreen) { + if (!DisplayServer::get_singleton()->is_touchscreen_available()) { //only if no touchscreen ui hint, set emulation id->set_emulate_touch_from_mouse(true); } @@ -3300,6 +3304,10 @@ void Main::cleanup(bool p_force) { ERR_FAIL_COND(!_start_success); } + for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) { + TextServerManager::get_singleton()->get_interface(i)->cleanup(); + } + if (movie_writer) { movie_writer->end(); } diff --git a/misc/dist/shell/_godot.zsh-completion b/misc/dist/shell/_godot.zsh-completion index 9b4ef52f3f..7b3e01d277 100644 --- a/misc/dist/shell/_godot.zsh-completion +++ b/misc/dist/shell/_godot.zsh-completion @@ -50,14 +50,15 @@ _arguments \ '--text-driver[set the text driver]:text driver name' \ '--tablet-driver[set the pen tablet input driver]:tablet driver name' \ '--headless[enable headless mode (--display-driver headless --audio-driver Dummy), useful for servers and with --script]' \ + '--write-movie[writes a video to the specified path (usually with .avi or .png extension)]:path to output video file' \ '(-f --fullscreen)'{-f,--fullscreen}'[request fullscreen mode]' \ '(-m --maximized)'{-m,--maximized}'[request a maximized window]' \ '(-w --windowed)'{-w,--windowed}'[request windowed mode]' \ '(-t --always-on-top)'{-t,--always-on-top}'[request an always-on-top window]' \ '--resolution[request window resolution]:resolution in WxH format' \ '--position[request window position]:position in X,Y format' \ - '--headless[enable headless mode (--display-driver headless --audio-driver Dummy). Useful for servers and with --script]' \ '--single-window[use a single window (no separate subwindows)]' \ + '--xr-mode[select Extended Reality (XR) mode]:Extended Reality (XR) mode:(default off on)' \ '(-d --debug)'{-d,--debug}'[debug (local stdout debugger)]' \ '(-b --breakpoints)'{-b,--breakpoints}'[specify the breakpoint list as source::line comma-separated pairs, no spaces (use %20 instead)]:breakpoint list' \ '--profiling[enable profiling in the script debugger]' \ @@ -70,6 +71,7 @@ _arguments \ '--debug-stringnames[print all StringName allocations to stdout when the engine quits]' \ '--frame-delay[simulate high CPU load (delay each frame by the given number of milliseconds)]:number of milliseconds' \ '--time-scale[force time scale (higher values are faster, 1.0 is normal speed)]:time scale' \ + '--disable-vsync[disable vertical synchronization even if enabled in the project settings]' \ '--disable-render-loop[disable render loop so rendering only occurs when called explicitly from script]' \ '--disable-crash-handler[disable crash handler when supported by the platform code]' \ '--fixed-fps[force a fixed number of frames per second (this setting disables real-time synchronization)]:frames per second' \ @@ -86,4 +88,6 @@ _arguments \ '--build-solutions[build the scripting solutions (e.g. for C# projects)]' \ '--dump-gdextension-interface[generate GDExtension header file 'gdnative_interface.h' in the current folder. This file is the base file required to implement a GDExtension.]' \ '--dump-extension-api[generate JSON dump of the Godot API for GDExtension bindings named "extension_api.json" in the current folder]' \ + '--startup-benchmark[benchmark the startup time and print it to console]' \ + '--startup-benchmark-file[benchmark the startup time and save it to a given file in JSON format]:path to output JSON file' \ '--test[run all unit tests; run with "--test --help" for more information]' diff --git a/misc/dist/shell/godot.bash-completion b/misc/dist/shell/godot.bash-completion index 02dcc94033..73107c0cf3 100644 --- a/misc/dist/shell/godot.bash-completion +++ b/misc/dist/shell/godot.bash-completion @@ -53,6 +53,7 @@ _complete_godot_options() { --text-driver --tablet-driver --headless +--write-movie --fullscreen --maximized --windowed @@ -60,6 +61,7 @@ _complete_godot_options() { --resolution --position --single-window +--xr-mode --debug --breakpoints --profiling @@ -72,6 +74,7 @@ _complete_godot_options() { --debug-stringnames --frame-delay --time-scale +--disable-vsync --disable-render-loop --disable-crash-handler --fixed-fps @@ -88,6 +91,8 @@ _complete_godot_options() { --build-solutions --dump-gdextension-interface --dump-extension-api +--startup-benchmark +--startup-benchmark-file --test " -- "$1")) } @@ -121,6 +126,10 @@ _complete_godot_bash() { local IFS=$' \n\t' # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "vulkan opengl3 dummy" -- "$cur")) + elif [[ $prev == "--xr-mode" ]]; then + local IFS=$' \n\t' + # shellcheck disable=SC2207 + COMPREPLY=($(compgen -W "default off on" -- "$cur")) elif [[ $prev == "--path" || $prev == "--doctool" ]]; then local IFS=$'\n\t' # shellcheck disable=SC2207 diff --git a/misc/dist/shell/godot.fish b/misc/dist/shell/godot.fish index 4f7803124b..c6edf75112 100644 --- a/misc/dist/shell/godot.fish +++ b/misc/dist/shell/godot.fish @@ -66,6 +66,7 @@ complete -c godot -l gpu-index -d "Use a specific GPU (run with --verbose to get complete -c godot -l text-driver -d "Set the text driver" -x complete -c godot -l tablet-driver -d "Set the pen tablet input driver" -x complete -c godot -l headless -d "Enable headless mode (--display-driver headless --audio-driver Dummy). Useful for servers and with --script" +complete -c godot -l write-movie -d "Writes a video to the specified path (usually with .avi or .png extension). --fixed-fps is forced when enabled" -x # Display options: complete -c godot -s f -l fullscreen -d "Request fullscreen mode" @@ -74,8 +75,8 @@ complete -c godot -s w -l windowed -d "Request windowed mode" complete -c godot -s t -l always-on-top -d "Request an always-on-top window" complete -c godot -l resolution -d "Request window resolution" -x complete -c godot -l position -d "Request window position" -x -complete -c godot -l headless -d "Enable headless mode (--display-driver headless --audio-driver Dummy). Useful for servers and with --script" complete -c godot -l single-window -d "Use a single window (no separate subwindows)" +complete -c godot -l xr-mode -d "Select Extended Reality (XR) mode" -a "default off on" # Debug options: complete -c godot -s d -l debug -d "Debug (local stdout debugger)" @@ -108,4 +109,6 @@ complete -c godot -l no-docbase -d "Disallow dumping the base types (used with - complete -c godot -l build-solutions -d "Build the scripting solutions (e.g. for C# projects)" complete -c godot -l dump-gdextension-interface -d "Generate GDExtension header file 'gdnative_interface.h' in the current folder. This file is the base file required to implement a GDExtension" complete -c godot -l dump-extension-api -d "Generate JSON dump of the Godot API for GDExtension bindings named 'extension_api.json' in the current folder" +complete -c godot -l startup-benchmark -d "Benchmark the startup time and print it to console" +complete -c godot -l startup-benchmark-file -d "Benchmark the startup time and save it to a given file in JSON format" -x complete -c godot -l test -d "Run all unit tests; run with '--test --help' for more information" -x diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 60230257e0..c8195de640 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -2019,6 +2019,42 @@ Error GDScriptLanguage::execute_file(const String &p_path) { } void GDScriptLanguage::finish() { + if (_call_stack) { + memdelete_arr(_call_stack); + _call_stack = nullptr; + } + + // Clear the cache before parsing the script_list + GDScriptCache::clear(); + + // Clear dependencies between scripts, to ensure cyclic references are broken + // (to avoid leaks at exit). + SelfList<GDScript> *s = script_list.first(); + while (s) { + // This ensures the current script is not released before we can check + // what's the next one in the list (we can't get the next upfront because we + // don't know if the reference breaking will cause it -or any other after + // it, for that matter- to be released so the next one is not the same as + // before). + Ref<GDScript> scr = s->self(); + if (scr.is_valid()) { + for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) { + GDScriptFunction *func = E.value; + for (int i = 0; i < func->argument_types.size(); i++) { + func->argument_types.write[i].script_type_ref = Ref<Script>(); + } + func->return_type.script_type_ref = Ref<Script>(); + } + for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) { + E.value.data_type.script_type_ref = Ref<Script>(); + } + + // Clear backup for scripts that could slip out of the cyclic reference + // check + scr->clear(); + } + s = s->next(); + } } void GDScriptLanguage::profiling_start() { @@ -2530,36 +2566,6 @@ GDScriptLanguage::GDScriptLanguage() { } GDScriptLanguage::~GDScriptLanguage() { - if (_call_stack) { - memdelete_arr(_call_stack); - } - - // Clear dependencies between scripts, to ensure cyclic references are broken (to avoid leaks at exit). - SelfList<GDScript> *s = script_list.first(); - while (s) { - // This ensures the current script is not released before we can check what's the next one - // in the list (we can't get the next upfront because we don't know if the reference breaking - // will cause it -or any other after it, for that matter- to be released so the next one - // is not the same as before). - Ref<GDScript> scr = s->self(); - if (scr.is_valid()) { - for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) { - GDScriptFunction *func = E.value; - for (int i = 0; i < func->argument_types.size(); i++) { - func->argument_types.write[i].script_type_ref = Ref<Script>(); - } - func->return_type.script_type_ref = Ref<Script>(); - } - for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) { - E.value.data_type.script_type_ref = Ref<Script>(); - } - - // Clear backup for scripts that could slip out of the cyclic reference check - scr->clear(); - } - s = s->next(); - } - singleton = nullptr; } diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 021504f242..699ce67933 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -381,15 +381,15 @@ void GDScriptCache::clear_unreferenced_packed_scenes() { } } -GDScriptCache::GDScriptCache() { - singleton = this; -} +void GDScriptCache::clear() { + if (singleton == nullptr) { + return; + } -GDScriptCache::~GDScriptCache() { - destructing = true; + MutexLock lock(singleton->mutex); RBSet<Ref<GDScriptParserRef>> parser_map_refs; - for (KeyValue<String, GDScriptParserRef *> &E : parser_map) { + for (KeyValue<String, GDScriptParserRef *> &E : singleton->parser_map) { parser_map_refs.insert(E.value); } @@ -399,12 +399,20 @@ GDScriptCache::~GDScriptCache() { } parser_map_refs.clear(); - parser_map.clear(); - shallow_gdscript_cache.clear(); - full_gdscript_cache.clear(); + singleton->parser_map.clear(); + singleton->shallow_gdscript_cache.clear(); + singleton->full_gdscript_cache.clear(); - packed_scene_cache.clear(); - packed_scene_dependencies.clear(); + singleton->packed_scene_cache.clear(); + singleton->packed_scene_dependencies.clear(); +} + +GDScriptCache::GDScriptCache() { + singleton = this; +} +GDScriptCache::~GDScriptCache() { + destructing = true; + clear(); singleton = nullptr; } diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index 2195932aa3..e7e1901d5d 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -111,6 +111,8 @@ public: return singleton->destructing; }; + static void clear(); + GDScriptCache(); ~GDScriptCache(); }; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 24241b712b..ea93e1ebfc 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -43,7 +43,7 @@ bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringN return false; } - if (codegen.parameters.has(p_name) || codegen.locals.has(p_name)) { + if (_is_local_or_parameter(codegen, p_name)) { return false; //shadowed } @@ -65,6 +65,10 @@ bool GDScriptCompiler::_is_class_member_property(GDScript *owner, const StringNa return ClassDB::has_property(nc->get_name(), p_name); } +bool GDScriptCompiler::_is_local_or_parameter(CodeGen &codegen, const StringName &p_name) { + return codegen.parameters.has(p_name) || codegen.locals.has(p_name); +} + void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::Node *p_node) { if (!error.is_empty()) { return; @@ -920,7 +924,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code StringName var_name = identifier->name; if (_is_class_member_property(codegen, var_name)) { assign_class_member_property = var_name; - } else if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { + } else if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) { is_member_property = true; member_property_setter_function = codegen.script->member_indices[var_name].setter; member_property_has_setter = member_property_setter_function != StringName(); @@ -1131,7 +1135,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool is_in_setter = false; StringName setter_function; StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { + if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) { is_member = true; setter_function = codegen.script->member_indices[var_name].setter; has_setter = setter_function != StringName(); diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 45ca4fe342..cba585e5a5 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -115,6 +115,7 @@ class GDScriptCompiler { bool _is_class_member_property(CodeGen &codegen, const StringName &p_name); bool _is_class_member_property(GDScript *owner, const StringName &p_name); + bool _is_local_or_parameter(CodeGen &codegen, const StringName &p_name); void _set_error(const String &p_error, const GDScriptParser::Node *p_node); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index c02ee99a86..1456612915 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -2512,50 +2512,62 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::SubscriptNode *p_subscript, GDScriptParser::DataType &r_base_type, Variant *r_base = nullptr) { - if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER && p_context.base != nullptr) { - const GDScriptParser::GetNodeNode *get_node = nullptr; - const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base); + if (p_context.base == nullptr) { + return false; + } + const GDScriptParser::GetNodeNode *get_node = nullptr; + + switch (p_subscript->base->type) { + case GDScriptParser::Node::GET_NODE: { + get_node = static_cast<GDScriptParser::GetNodeNode *>(p_subscript->base); + } break; - switch (identifier_node->source) { - case GDScriptParser::IdentifierNode::Source::MEMBER_VARIABLE: { - if (p_context.current_class != nullptr) { - const StringName &member_name = identifier_node->name; - const GDScriptParser::ClassNode *current_class = p_context.current_class; + case GDScriptParser::Node::IDENTIFIER: { + const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base); - if (current_class->has_member(member_name)) { - const GDScriptParser::ClassNode::Member &member = current_class->get_member(member_name); + switch (identifier_node->source) { + case GDScriptParser::IdentifierNode::Source::MEMBER_VARIABLE: { + if (p_context.current_class != nullptr) { + const StringName &member_name = identifier_node->name; + const GDScriptParser::ClassNode *current_class = p_context.current_class; - if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { - const GDScriptParser::VariableNode *variable = static_cast<GDScriptParser::VariableNode *>(member.variable); + if (current_class->has_member(member_name)) { + const GDScriptParser::ClassNode::Member &member = current_class->get_member(member_name); - if (variable->initializer && variable->initializer->type == GDScriptParser::Node::GET_NODE) { - get_node = static_cast<GDScriptParser::GetNodeNode *>(variable->initializer); + if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { + const GDScriptParser::VariableNode *variable = static_cast<GDScriptParser::VariableNode *>(member.variable); + + if (variable->initializer && variable->initializer->type == GDScriptParser::Node::GET_NODE) { + get_node = static_cast<GDScriptParser::GetNodeNode *>(variable->initializer); + } } } } - } - } break; - case GDScriptParser::IdentifierNode::Source::LOCAL_VARIABLE: { - if (identifier_node->next != nullptr && identifier_node->next->type == GDScriptParser::ClassNode::Node::GET_NODE) { - get_node = static_cast<GDScriptParser::GetNodeNode *>(identifier_node->next); - } - } break; - default: - break; - } + } break; + case GDScriptParser::IdentifierNode::Source::LOCAL_VARIABLE: { + if (identifier_node->next != nullptr && identifier_node->next->type == GDScriptParser::ClassNode::Node::GET_NODE) { + get_node = static_cast<GDScriptParser::GetNodeNode *>(identifier_node->next); + } + } break; + default: { + } break; + } + } break; + default: { + } break; + } - if (get_node != nullptr) { - const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path)); - if (node != nullptr) { - if (r_base != nullptr) { - *r_base = node; - } - r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - r_base_type.kind = GDScriptParser::DataType::NATIVE; - r_base_type.native_type = node->get_class_name(); - r_base_type.builtin_type = Variant::OBJECT; - return true; + if (get_node != nullptr) { + const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path)); + if (node != nullptr) { + if (r_base != nullptr) { + *r_base = node; } + r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_base_type.kind = GDScriptParser::DataType::NATIVE; + r_base_type.native_type = node->get_class_name(); + r_base_type.builtin_type = Variant::OBJECT; + return true; } } @@ -2612,7 +2624,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } } - if (p_context.base != nullptr && subscript->is_attribute) { + if (subscript->is_attribute) { bool found_type = _get_subscript_type(p_context, subscript, base_type, &base); if (!found_type) { @@ -3276,6 +3288,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co parser.parse(p_code, p_path, true); GDScriptParser::CompletionContext context = parser.get_completion_context(); + context.base = p_owner; // Allows class functions with the names like built-ins to be handled properly. if (context.type != GDScriptParser::COMPLETION_ATTRIBUTE) { @@ -3448,7 +3461,9 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co break; } GDScriptCompletionIdentifier base; - if (!_guess_expression_type(context, subscript->base, base)) { + + bool found_type = _get_subscript_type(context, subscript, base.type); + if (!found_type && !_guess_expression_type(context, subscript->base, base)) { break; } diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index de3becbaf8..e442bf8159 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -844,8 +844,9 @@ Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) { lines = p_code.split("\n"); Error err = GDScriptParser::parse(p_code, p_path, false); + GDScriptAnalyzer analyzer(this); + if (err == OK) { - GDScriptAnalyzer analyzer(this); err = analyzer.analyze(); } update_diagnostics(); diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd new file mode 100644 index 0000000000..f33ba7dffd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd @@ -0,0 +1,25 @@ +# https://github.com/godotengine/godot/pull/69620 + +var a: int = 1 + +func shadow_regular_assignment(a: Variant, b: Variant) -> void: + print(a) + print(self.a) + a = b + print(a) + print(self.a) + + +var v := Vector2(0.0, 0.0) + +func shadow_subscript_assignment(v: Vector2, x: float) -> void: + print(v) + print(self.v) + v.x += x + print(v) + print(self.v) + + +func test(): + shadow_regular_assignment('a', 'b') + shadow_subscript_assignment(Vector2(1.0, 1.0), 5.0) diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out new file mode 100644 index 0000000000..5b981bc8bb --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out @@ -0,0 +1,17 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> SHADOWED_VARIABLE +>> The local function parameter "a" is shadowing an already-declared variable at line 3. +>> WARNING +>> Line: 15 +>> SHADOWED_VARIABLE +>> The local function parameter "v" is shadowing an already-declared variable at line 13. +a +1 +b +1 +(1, 1) +(0, 0) +(6, 1) +(0, 0) diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp index 3cf49a3046..a194719b91 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp @@ -63,7 +63,12 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t if (p_options.has("animation/import")) { state->set_create_animations(bool(p_options["animation/import"])); } - return doc->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]); + + if (p_options.has("animation/trimming")) { + return doc->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]); + } else { + return doc->generate_scene(state, (float)p_options["animation/fps"], false); + } } #endif // TOOLS_ENABLED diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 735e35ac1e..a3685daf0e 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -796,7 +796,7 @@ Error GLTFDocument::_parse_buffers(Ref<GLTFState> state, const String &p_base_pa ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER); uri = uri.uri_decode(); uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows. - buffer_data = FileAccess::get_file_as_array(uri); + buffer_data = FileAccess::get_file_as_bytes(uri); ERR_FAIL_COND_V_MSG(buffer.size() == 0, ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri); } @@ -3139,7 +3139,7 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> state, const String &p_base_pat // Fallback to loading as byte array. // This enables us to support the spec's requirement that we honor mimetype // regardless of file URI. - data = FileAccess::get_file_as_array(uri); + data = FileAccess::get_file_as_bytes(uri); if (data.size() == 0) { WARN_PRINT(vformat("glTF: Image index '%d' couldn't be loaded as a buffer of MIME type '%s' from URI: %s. Skipping it.", i, mimetype, uri)); state->images.push_back(Ref<Texture2D>()); // Placeholder to keep count. diff --git a/modules/openxr/extensions/openxr_opengl_extension.cpp b/modules/openxr/extensions/openxr_opengl_extension.cpp index 234c5f8391..3b7c130149 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.cpp +++ b/modules/openxr/extensions/openxr_opengl_extension.cpp @@ -136,7 +136,7 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR; graphics_binding_gl.next = p_next_pointer; - graphics_binding_gl.display = eglGetCurrentDisplay(); + graphics_binding_gl.display = (void *)display_server->window_get_native_handle(DisplayServer::DISPLAY_HANDLE); graphics_binding_gl.config = (EGLConfig)0; // https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/master/src/tests/hello_xr/graphicsplugin_opengles.cpp#L122 graphics_binding_gl.context = (void *)display_server->window_get_native_handle(DisplayServer::OPENGL_CONTEXT); #else diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 59d3865acd..b652ca4617 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -740,9 +740,10 @@ bool OpenXRAPI::create_swapchains() { ERR_FAIL_NULL_V_MSG(projection_views, false, "OpenXR Couldn't allocate memory for projection views"); // We create our depth swapchain if: + // - we've enabled submitting depth buffer // - we support our depth layer extension // - we have our spacewarp extension (not yet implemented) - if (OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { + if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { // Build a vector with swapchain formats we want to use, from best fit to worst Vector<int64_t> usable_swapchain_formats; int64_t swapchain_format_to_use = 0; @@ -790,13 +791,13 @@ bool OpenXRAPI::create_swapchains() { projection_views[i].subImage.imageRect.extent.width = recommended_size.width; projection_views[i].subImage.imageRect.extent.height = recommended_size.height; - if (OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && depth_views) { + if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && depth_views) { projection_views[i].next = &depth_views[i]; depth_views[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR; depth_views[i].next = nullptr; depth_views[i].subImage.swapchain = swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain; - depth_views[i].subImage.imageArrayIndex = 0; + depth_views[i].subImage.imageArrayIndex = i; depth_views[i].subImage.imageRect.offset.x = 0; depth_views[i].subImage.imageRect.offset.y = 0; depth_views[i].subImage.imageRect.extent.width = recommended_size.width; @@ -1066,6 +1067,30 @@ bool OpenXRAPI::on_state_exiting() { return true; } +void OpenXRAPI::set_form_factor(XrFormFactor p_form_factor) { + ERR_FAIL_COND(is_initialized()); + + form_factor = p_form_factor; +} + +void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configuration) { + ERR_FAIL_COND(is_initialized()); + + view_configuration = p_view_configuration; +} + +void OpenXRAPI::set_reference_space(XrReferenceSpaceType p_reference_space) { + ERR_FAIL_COND(is_initialized()); + + reference_space = p_reference_space; +} + +void OpenXRAPI::set_submit_depth_buffer(bool p_submit_depth_buffer) { + ERR_FAIL_COND(is_initialized()); + + submit_depth_buffer = p_submit_depth_buffer; +} + bool OpenXRAPI::is_initialized() { return (instance != XR_NULL_HANDLE); } @@ -1684,7 +1709,7 @@ RID OpenXRAPI::get_color_texture() { } RID OpenXRAPI::get_depth_texture() { - if (swapchains[OPENXR_SWAPCHAIN_DEPTH].image_acquired) { + if (submit_depth_buffer && swapchains[OPENXR_SWAPCHAIN_DEPTH].image_acquired) { return graphics_extension->get_texture(swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data, swapchains[OPENXR_SWAPCHAIN_DEPTH].image_index); } else { return RID(); @@ -1862,6 +1887,8 @@ OpenXRAPI::OpenXRAPI() { default: break; } + + submit_depth_buffer = GLOBAL_GET("xr/openxr/submit_depth_buffer"); } // reset a few things that can't be done in our class definition diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 5dce749351..ac4bbff94c 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -104,6 +104,7 @@ private: XrViewConfigurationType view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; // XrEnvironmentBlendMode environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; + bool submit_depth_buffer = false; // if set to true we submit depth buffers to OpenXR if a suitable extension is enabled. // state XrInstance instance = XR_NULL_HANDLE; @@ -312,6 +313,18 @@ public: void set_xr_interface(OpenXRInterface *p_xr_interface); void register_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper); + void set_form_factor(XrFormFactor p_form_factor); + XrFormFactor get_form_factor() const { return form_factor; } + + void set_view_configuration(XrViewConfigurationType p_view_configuration); + XrViewConfigurationType get_view_configuration() const { return view_configuration; } + + void set_reference_space(XrReferenceSpaceType p_reference_space); + XrReferenceSpaceType get_reference_space() const { return reference_space; } + + void set_submit_depth_buffer(bool p_submit_depth_buffer); + bool get_submit_depth_buffer() const { return submit_depth_buffer; } + bool is_initialized(); bool is_running(); bool initialize(const String &p_rendering_driver); diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index 77660eb6f0..40190ab2f3 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -45,7 +45,7 @@ void OpenXRInterface::_bind_methods() { // Display refresh rate ClassDB::bind_method(D_METHOD("get_display_refresh_rate"), &OpenXRInterface::get_display_refresh_rate); ClassDB::bind_method(D_METHOD("set_display_refresh_rate", "refresh_rate"), &OpenXRInterface::set_display_refresh_rate); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "display_refresh_rate"), "set_display_refresh_rate", "get_display_refresh_rate"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "display_refresh_rate"), "set_display_refresh_rate", "get_display_refresh_rate"); ClassDB::bind_method(D_METHOD("get_available_display_refresh_rates"), &OpenXRInterface::get_available_display_refresh_rates); } diff --git a/modules/raycast/SCsub b/modules/raycast/SCsub index 20b05816e1..51d75d45b0 100644 --- a/modules/raycast/SCsub +++ b/modules/raycast/SCsub @@ -67,7 +67,7 @@ if env["builtin_embree"]: env_raycast.AppendUnique(CPPDEFINES=["NDEBUG"]) # No assert() even in debug builds. if not env.msvc: - if env["arch"] == "x86_64": + if env["arch"] in ["x86_64", "x86_32"]: env_raycast.Append(CPPFLAGS=["-msse2", "-mxsave"]) if env["platform"] == "windows": @@ -87,10 +87,13 @@ if env["builtin_embree"]: env_thirdparty.disable_warnings() env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) - if env["arch"] == "arm64" or env.msvc: + if env["arch"] != "x86_64" or env.msvc: # Embree needs those, it will automatically use SSE2NEON in ARM env_thirdparty.Append(CPPDEFINES=["__SSE2__", "__SSE__"]) + if env["platform"] == "web": + env_thirdparty.Append(CPPFLAGS=["-msimd128"]) + if not env.msvc: env_thirdparty.Append( CPPFLAGS=[ diff --git a/modules/raycast/config.py b/modules/raycast/config.py index 833ad50018..f4243f01c5 100644 --- a/modules/raycast/config.py +++ b/modules/raycast/config.py @@ -1,9 +1,13 @@ def can_build(env, platform): - # Depends on Embree library, which only supports x86_64 and arm64. - if platform == "windows": - return env["arch"] == "x86_64" # TODO build for Windows on ARM - - return env["arch"] in ["x86_64", "arm64"] + # Supported architectures depend on the Embree library. + # No ARM32 support planned. + if env["arch"] == "arm32": + return False + # x86_32 only seems supported on Windows for now. + if env["arch"] == "x86_32" and platform != "windows": + return False + # The rest works, even wasm32! + return True def configure(env): diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index cf2d8c9986..27fab88956 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -34,6 +34,7 @@ // Headers for building as GDExtension plug-in. #include <godot_cpp/classes/file_access.hpp> +#include <godot_cpp/classes/os.hpp> #include <godot_cpp/classes/project_settings.hpp> #include <godot_cpp/classes/rendering_server.hpp> #include <godot_cpp/classes/translation_server.hpp> @@ -1437,11 +1438,13 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f if (fd->face->style_name != nullptr) { p_font_data->style_name = String::utf8((const char *)fd->face->style_name); } + p_font_data->weight = _font_get_weight_by_name(p_font_data->style_name.to_lower()); + p_font_data->stretch = _font_get_stretch_by_name(p_font_data->style_name.to_lower()); p_font_data->style_flags = 0; - if (fd->face->style_flags & FT_STYLE_FLAG_BOLD) { + if ((fd->face->style_flags & FT_STYLE_FLAG_BOLD) || p_font_data->weight >= 700) { p_font_data->style_flags.set_flag(FONT_BOLD); } - if (fd->face->style_flags & FT_STYLE_FLAG_ITALIC) { + if ((fd->face->style_flags & FT_STYLE_FLAG_ITALIC) || _is_ital_style(p_font_data->style_name.to_lower())) { p_font_data->style_flags.set_flag(FONT_ITALIC); } if (fd->face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) { @@ -1967,6 +1970,46 @@ String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const { return fd->style_name; } +void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weight) { + FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->weight = CLAMP(p_weight, 100, 999); +} + +int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const { + FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND_V(!fd, 400); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 400); + return fd->weight; +} + +void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stretch) { + FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->stretch = CLAMP(p_stretch, 50, 200); +} + +int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const { + FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND_V(!fd, 100); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 100); + return fd->stretch; +} + void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_name) { FontAdvanced *fd = font_owner.get_or_null(p_font_rid); ERR_FAIL_COND(!fd); @@ -2103,6 +2146,25 @@ int64_t TextServerAdvanced::_font_get_fixed_size(const RID &p_font_rid) const { return fd->fixed_size; } +void TextServerAdvanced::_font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) { + FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->allow_system_fallback != p_allow_system_fallback) { + _font_clear_cache(fd); + fd->allow_system_fallback = p_allow_system_fallback; + } +} + +bool TextServerAdvanced::_font_is_allow_system_fallback(const RID &p_font_rid) const { + FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->allow_system_fallback; +} + void TextServerAdvanced::_font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) { FontAdvanced *fd = font_owner.get_or_null(p_font_rid); ERR_FAIL_COND(!fd); @@ -4632,12 +4694,11 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) { sd->breaks[pos] = true; } else if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_SOFT) && (ubrk_getRuleStatus(bi) < UBRK_LINE_SOFT_LIMIT)) { sd->breaks[pos] = false; - - int pos_p = pos - 1 - sd->start; - char32_t c = sd->text[pos_p]; - if (pos - sd->start != sd->end && !is_whitespace(c) && (c != 0xfffc)) { - sd->break_inserts++; - } + } + int pos_p = pos - 1 - sd->start; + char32_t c = sd->text[pos_p]; + if (pos - sd->start != sd->end && !is_whitespace(c) && (c != 0xfffc)) { + sd->break_inserts++; } } } @@ -5066,10 +5127,177 @@ _FORCE_INLINE_ void TextServerAdvanced::_add_featuers(const Dictionary &p_source } } -void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_end, hb_script_t p_script, hb_direction_t p_direction, TypedArray<RID> p_fonts, int64_t p_span, int64_t p_fb_index) { +void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_end, hb_script_t p_script, hb_direction_t p_direction, TypedArray<RID> p_fonts, int64_t p_span, int64_t p_fb_index, int64_t p_prev_start, int64_t p_prev_end) { + RID f; int fs = p_sd->spans[p_span].font_size; - if (p_fb_index >= p_fonts.size()) { - // Add fallback glyphs. + + if (p_fb_index >= 0 && p_fb_index < p_fonts.size()) { + // Try font from list. + f = p_fonts[p_fb_index]; + } else if (OS::get_singleton()->has_feature("system_fonts") && p_fonts.size() > 0 && ((p_fb_index == p_fonts.size()) || (p_fb_index > p_fonts.size() && p_start != p_prev_start))) { + // Try system fallback. + RID fdef = p_fonts[0]; + if (_font_is_allow_system_fallback(fdef)) { + String text = p_sd->text.substr(p_start, 1); + String font_name = _font_get_name(fdef); + BitField<FontStyle> font_style = _font_get_style(fdef); + int font_weight = _font_get_weight(fdef); + int font_stretch = _font_get_stretch(fdef); + Dictionary dvar = _font_get_variation_coordinates(fdef); + static int64_t wgth_tag = _name_to_tag("weight"); + static int64_t wdth_tag = _name_to_tag("width"); + static int64_t ital_tag = _name_to_tag("italic"); + if (dvar.has(wgth_tag)) { + font_weight = dvar[wgth_tag].operator int(); + } + if (dvar.has(wdth_tag)) { + font_stretch = dvar[wdth_tag].operator int(); + } + if (dvar.has(ital_tag) && dvar[ital_tag].operator int() == 1) { + font_style.set_flag(TextServer::FONT_ITALIC); + } + + char scr_buffer[5] = { 0, 0, 0, 0, 0 }; + hb_tag_to_string(hb_script_to_iso15924_tag(p_script), scr_buffer); + String script_code = String(scr_buffer); + String locale = (p_sd->spans[p_span].language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_sd->spans[p_span].language; + + PackedStringArray fallback_font_name = OS::get_singleton()->get_system_font_path_for_text(font_name, text, locale, script_code, font_weight, font_stretch, font_style & TextServer::FONT_ITALIC); +#ifdef GDEXTENSION + for (int fb = 0; fb < fallback_font_name.size(); fb++) { + const String &E = fallback_font_name[fb]; +#else + for (const String &E : fallback_font_name) { +#endif + SystemFontKey key = SystemFontKey(E, font_style & TextServer::FONT_ITALIC, font_weight, font_stretch, fdef, this); + if (system_fonts.has(key)) { + const SystemFontCache &sysf_cache = system_fonts[key]; + int best_score = 0; + int best_match = -1; + for (int face_idx = 0; face_idx < sysf_cache.var.size(); face_idx++) { + const SystemFontCacheRec &F = sysf_cache.var[face_idx]; + if (unlikely(!_font_has_char(F.rid, text[0]))) { + continue; + } + BitField<FontStyle> style = _font_get_style(F.rid); + int weight = _font_get_weight(F.rid); + int stretch = _font_get_stretch(F.rid); + int score = (20 - Math::abs(weight - font_weight) / 50); + score += (20 - Math::abs(stretch - font_stretch) / 10); + if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) { + score += 30; + } + if (score >= best_score) { + best_score = score; + best_match = face_idx; + } + if (best_score == 70) { + break; + } + } + if (best_match != -1) { + f = sysf_cache.var[best_match].rid; + } + } + if (!f.is_valid()) { + if (system_fonts.has(key)) { + const SystemFontCache &sysf_cache = system_fonts[key]; + if (sysf_cache.max_var == sysf_cache.var.size()) { + // All subfonts already tested, skip. + continue; + } + } + + if (!system_font_data.has(E)) { + system_font_data[E] = FileAccess::get_file_as_bytes(E); + } + + const PackedByteArray &font_data = system_font_data[E]; + + SystemFontCacheRec sysf; + sysf.rid = _create_font(); + _font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size()); + + Dictionary var = dvar; + // Select matching style from collection. + int best_score = 0; + int best_match = -1; + for (int face_idx = 0; face_idx < _font_get_face_count(sysf.rid); face_idx++) { + _font_set_face_index(sysf.rid, face_idx); + if (unlikely(!_font_has_char(sysf.rid, text[0]))) { + continue; + } + BitField<FontStyle> style = _font_get_style(sysf.rid); + int weight = _font_get_weight(sysf.rid); + int stretch = _font_get_stretch(sysf.rid); + int score = (20 - Math::abs(weight - font_weight) / 50); + score += (20 - Math::abs(stretch - font_stretch) / 10); + if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) { + score += 30; + } + if (score >= best_score) { + best_score = score; + best_match = face_idx; + } + if (best_score == 70) { + break; + } + } + if (best_match == -1) { + _free_rid(sysf.rid); + continue; + } else { + _font_set_face_index(sysf.rid, best_match); + } + sysf.index = best_match; + + // If it's a variable font, apply weight, stretch and italic coordinates to match requested style. + if (best_score != 70) { + Dictionary ftr = _font_supported_variation_list(sysf.rid); + if (ftr.has(wdth_tag)) { + var[wdth_tag] = font_stretch; + _font_set_stretch(sysf.rid, font_stretch); + } + if (ftr.has(wgth_tag)) { + var[wgth_tag] = font_weight; + _font_set_weight(sysf.rid, font_weight); + } + if ((font_style & TextServer::FONT_ITALIC) && ftr.has(ital_tag)) { + var[ital_tag] = 1; + _font_set_style(sysf.rid, _font_get_style(sysf.rid) | TextServer::FONT_ITALIC); + } + } + + _font_set_antialiasing(sysf.rid, key.antialiasing); + _font_set_generate_mipmaps(sysf.rid, key.mipmaps); + _font_set_multichannel_signed_distance_field(sysf.rid, key.msdf); + _font_set_msdf_pixel_range(sysf.rid, key.msdf_range); + _font_set_msdf_size(sysf.rid, key.msdf_source_size); + _font_set_fixed_size(sysf.rid, key.fixed_size); + _font_set_force_autohinter(sysf.rid, key.force_autohinter); + _font_set_hinting(sysf.rid, key.hinting); + _font_set_subpixel_positioning(sysf.rid, key.subpixel_positioning); + _font_set_variation_coordinates(sysf.rid, var); + _font_set_oversampling(sysf.rid, key.oversampling); + _font_set_embolden(sysf.rid, key.embolden); + _font_set_transform(sysf.rid, key.transform); + + if (system_fonts.has(key)) { + system_fonts[key].var.push_back(sysf); + } else { + SystemFontCache &sysf_cache = system_fonts[key]; + sysf_cache.max_var = _font_get_face_count(sysf.rid); + sysf_cache.var.push_back(sysf); + } + f = sysf.rid; + } + break; + } + } + } + + if (!f.is_valid()) { + // No valid font, use fallback hex code boxes. for (int i = p_start; i < p_end; i++) { if (p_sd->preserve_invalid || (p_sd->preserve_control && is_control(p_sd->text[i]))) { Glyph gl; @@ -5100,7 +5328,6 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star return; } - RID f = p_fonts[p_fb_index]; FontAdvanced *fd = font_owner.get_or_null(f); ERR_FAIL_COND(!fd); MutexLock lock(fd->mutex); @@ -5195,7 +5422,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star gl.end = end; gl.count = 0; - gl.font_rid = p_fonts[p_fb_index]; + gl.font_rid = f; gl.font_size = fs; if (glyph_info[i].mask & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) { @@ -5262,7 +5489,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star for (unsigned int i = 0; i < glyph_count; i++) { if ((w[i].flags & GRAPHEME_IS_VALID) == GRAPHEME_IS_VALID) { if (failed_subrun_start != p_end + 1) { - _shape_run(p_sd, failed_subrun_start, failed_subrun_end, p_script, p_direction, p_fonts, p_span, p_fb_index + 1); + _shape_run(p_sd, failed_subrun_start, failed_subrun_end, p_script, p_direction, p_fonts, p_span, p_fb_index + 1, p_start, p_end); failed_subrun_start = p_end + 1; failed_subrun_end = p_start; } @@ -5292,7 +5519,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star } memfree(w); if (failed_subrun_start != p_end + 1) { - _shape_run(p_sd, failed_subrun_start, failed_subrun_end, p_script, p_direction, p_fonts, p_span, p_fb_index + 1); + _shape_run(p_sd, failed_subrun_start, failed_subrun_end, p_script, p_direction, p_fonts, p_span, p_fb_index + 1, p_start, p_end); } p_sd->ascent = MAX(p_sd->ascent, _font_get_ascent(f, fs)); p_sd->descent = MAX(p_sd->descent, _font_get_descent(f, fs)); @@ -5464,7 +5691,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { } fonts.append_array(fonts_scr_only); fonts.append_array(fonts_no_match); - _shape_run(sd, MAX(sd->spans[k].start - sd->start, script_run_start), MIN(sd->spans[k].end - sd->start, script_run_end), sd->script_iter->script_ranges[j].script, bidi_run_direction, fonts, k, 0); + _shape_run(sd, MAX(sd->spans[k].start - sd->start, script_run_start), MIN(sd->spans[k].end - sd->start, script_run_end), sd->script_iter->script_ranges[j].script, bidi_run_direction, fonts, k, 0, 0, 0); } } } @@ -5961,7 +6188,11 @@ String TextServerAdvanced::_strip_diacritics(const String &p_string) const { String result; for (int i = 0; i < normalized_string.length(); i++) { if (u_getCombiningClass(normalized_string[i]) == 0) { +#ifdef GDEXTENSION + result = result + String::chr(normalized_string[i]); +#else result = result + normalized_string[i]; +#endif } } return result; @@ -6243,6 +6474,17 @@ TextServerAdvanced::TextServerAdvanced() { _bmp_create_font_funcs(); } +void TextServerAdvanced::_cleanup() { + for (const KeyValue<SystemFontKey, SystemFontCache> &E : system_fonts) { + const Vector<SystemFontCacheRec> &sysf_cache = E.value.var; + for (const SystemFontCacheRec &F : sysf_cache) { + _free_rid(F.rid); + } + } + system_fonts.clear(); + system_font_data.clear(); +} + TextServerAdvanced::~TextServerAdvanced() { _bmp_free_font_funcs(); #ifdef MODULE_FREETYPE_ENABLED diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index 10fe3c2316..5e6d2cc2c0 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -300,6 +300,7 @@ class TextServerAdvanced : public TextServerExtension { int msdf_range = 14; int msdf_source_size = 48; int fixed_size = 0; + bool allow_system_fallback = true; bool force_autohinter = false; TextServer::Hinting hinting = TextServer::HINTING_LIGHT; TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; @@ -311,6 +312,8 @@ class TextServerAdvanced : public TextServerExtension { BitField<TextServer::FontStyle> style_flags = 0; String font_name; String style_name; + int weight = 400; + int stretch = 100; HashMap<Vector2i, FontForSizeAdvanced *, VariantHasher, VariantComparator> cache; @@ -372,6 +375,57 @@ class TextServerAdvanced : public TextServerExtension { _FORCE_INLINE_ double _get_extra_advance(RID p_font_rid, int p_font_size) const; _FORCE_INLINE_ Variant::Type _get_tag_type(int64_t p_tag) const; _FORCE_INLINE_ bool _get_tag_hidden(int64_t p_tag) const; + _FORCE_INLINE_ int _font_get_weight_by_name(const String &p_sty_name) const { + String sty_name = p_sty_name.replace(" ", "").replace("-", ""); + if (sty_name.find("thin") >= 0 || sty_name.find("hairline") >= 0) { + return 100; + } else if (sty_name.find("extralight") >= 0 || sty_name.find("ultralight") >= 0) { + return 200; + } else if (sty_name.find("light") >= 0) { + return 300; + } else if (sty_name.find("semilight") >= 0) { + return 350; + } else if (sty_name.find("regular") >= 0) { + return 400; + } else if (sty_name.find("medium") >= 0) { + return 500; + } else if (sty_name.find("semibold") >= 0 || sty_name.find("demibold") >= 0) { + return 600; + } else if (sty_name.find("bold") >= 0) { + return 700; + } else if (sty_name.find("extrabold") >= 0 || sty_name.find("ultrabold") >= 0) { + return 800; + } else if (sty_name.find("black") >= 0 || sty_name.find("heavy") >= 0) { + return 900; + } else if (sty_name.find("extrablack") >= 0 || sty_name.find("ultrablack") >= 0) { + return 950; + } + return 400; + } + _FORCE_INLINE_ int _font_get_stretch_by_name(const String &p_sty_name) const { + String sty_name = p_sty_name.replace(" ", "").replace("-", ""); + if (sty_name.find("ultracondensed") >= 0) { + return 50; + } else if (sty_name.find("extracondensed") >= 0) { + return 63; + } else if (sty_name.find("condensed") >= 0) { + return 75; + } else if (sty_name.find("semicondensed") >= 0) { + return 87; + } else if (sty_name.find("semiexpanded") >= 0) { + return 113; + } else if (sty_name.find("expanded") >= 0) { + return 125; + } else if (sty_name.find("extraexpanded") >= 0) { + return 150; + } else if (sty_name.find("ultraexpanded") >= 0) { + return 200; + } + return 100; + } + _FORCE_INLINE_ bool _is_ital_style(const String &p_sty_name) const { + return (p_sty_name.find("italic") >= 0) || (p_sty_name.find("oblique") >= 0); + } // Shaped text cache data. struct TrimData { @@ -474,12 +528,87 @@ class TextServerAdvanced : public TextServerExtension { mutable RID_PtrOwner<FontAdvanced> font_owner; mutable RID_PtrOwner<ShapedTextDataAdvanced> shaped_owner; + struct SystemFontKey { + String font_name; + TextServer::FontAntialiasing antialiasing = TextServer::FONT_ANTIALIASING_GRAY; + bool italic = false; + bool mipmaps = false; + bool msdf = false; + bool force_autohinter = false; + int weight = 400; + int stretch = 100; + int msdf_range = 14; + int msdf_source_size = 48; + int fixed_size = 0; + TextServer::Hinting hinting = TextServer::HINTING_LIGHT; + TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; + Dictionary variation_coordinates; + double oversampling = 0.0; + double embolden = 0.0; + Transform2D transform; + + bool operator==(const SystemFontKey &p_b) const { + return (font_name == p_b.font_name) && (antialiasing == p_b.antialiasing) && (italic == p_b.italic) && (mipmaps == p_b.mipmaps) && (msdf == p_b.msdf) && (force_autohinter == p_b.force_autohinter) && (weight == p_b.weight) && (stretch == p_b.stretch) && (msdf_range == p_b.msdf_range) && (msdf_source_size == p_b.msdf_source_size) && (fixed_size == p_b.fixed_size) && (hinting == p_b.hinting) && (subpixel_positioning == p_b.subpixel_positioning) && (variation_coordinates == p_b.variation_coordinates) && (oversampling == p_b.oversampling) && (embolden == p_b.embolden) && (transform == p_b.transform); + } + + SystemFontKey(const String &p_font_name, bool p_italic, int p_weight, int p_stretch, RID p_font, const TextServerAdvanced *p_fb) { + font_name = p_font_name; + italic = p_italic; + weight = p_weight; + stretch = p_stretch; + antialiasing = p_fb->_font_get_antialiasing(p_font); + mipmaps = p_fb->_font_get_generate_mipmaps(p_font); + msdf = p_fb->_font_is_multichannel_signed_distance_field(p_font); + msdf_range = p_fb->_font_get_msdf_pixel_range(p_font); + msdf_source_size = p_fb->_font_get_msdf_size(p_font); + fixed_size = p_fb->_font_get_fixed_size(p_font); + force_autohinter = p_fb->_font_is_force_autohinter(p_font); + hinting = p_fb->_font_get_hinting(p_font); + subpixel_positioning = p_fb->_font_get_subpixel_positioning(p_font); + variation_coordinates = p_fb->_font_get_variation_coordinates(p_font); + oversampling = p_fb->_font_get_oversampling(p_font); + embolden = p_fb->_font_get_embolden(p_font); + transform = p_fb->_font_get_transform(p_font); + } + }; + + struct SystemFontCacheRec { + RID rid; + int index = 0; + }; + + struct SystemFontCache { + Vector<SystemFontCacheRec> var; + int max_var = 0; + }; + + struct SystemFontKeyHasher { + _FORCE_INLINE_ static uint32_t hash(const SystemFontKey &p_a) { + uint32_t hash = p_a.font_name.hash(); + hash = hash_murmur3_one_32(p_a.variation_coordinates.hash(), hash); + hash = hash_murmur3_one_32(p_a.weight, hash); + hash = hash_murmur3_one_32(p_a.stretch, hash); + hash = hash_murmur3_one_32(p_a.msdf_range, hash); + hash = hash_murmur3_one_32(p_a.msdf_source_size, hash); + hash = hash_murmur3_one_32(p_a.fixed_size, hash); + hash = hash_murmur3_one_double(p_a.oversampling, hash); + hash = hash_murmur3_one_double(p_a.embolden, hash); + hash = hash_murmur3_one_real(p_a.transform[0].x, hash); + hash = hash_murmur3_one_real(p_a.transform[0].y, hash); + hash = hash_murmur3_one_real(p_a.transform[1].x, hash); + hash = hash_murmur3_one_real(p_a.transform[1].y, hash); + return hash_fmix32(hash_murmur3_one_32(((int)p_a.mipmaps) | ((int)p_a.msdf << 1) | ((int)p_a.italic << 2) | ((int)p_a.force_autohinter << 3) | ((int)p_a.hinting << 4) | ((int)p_a.subpixel_positioning << 8) | ((int)p_a.antialiasing << 12), hash)); + } + }; + mutable HashMap<SystemFontKey, SystemFontCache, SystemFontKeyHasher> system_fonts; + mutable HashMap<String, PackedByteArray> system_font_data; + void _realign(ShapedTextDataAdvanced *p_sd) const; int64_t _convert_pos(const String &p_utf32, const Char16String &p_utf16, int64_t p_pos) const; int64_t _convert_pos(const ShapedTextDataAdvanced *p_sd, int64_t p_pos) const; int64_t _convert_pos_inv(const ShapedTextDataAdvanced *p_sd, int64_t p_pos) const; bool _shape_substr(ShapedTextDataAdvanced *p_new_sd, const ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_length) const; - void _shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_end, hb_script_t p_script, hb_direction_t p_direction, TypedArray<RID> p_fonts, int64_t p_span, int64_t p_fb_index); + void _shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_end, hb_script_t p_script, hb_direction_t p_direction, TypedArray<RID> p_fonts, int64_t p_span, int64_t p_fb_index, int64_t p_prev_start, int64_t p_prev_end); Glyph _shape_single_glyph(ShapedTextDataAdvanced *p_sd, char32_t p_char, hb_script_t p_script, hb_direction_t p_direction, const RID &p_font, int64_t p_font_size); _FORCE_INLINE_ void _add_featuers(const Dictionary &p_source, Vector<hb_feature_t> &r_ftrs); @@ -568,6 +697,12 @@ public: MODBIND2(font_set_style_name, const RID &, const String &); MODBIND1RC(String, font_get_style_name, const RID &); + MODBIND2(font_set_weight, const RID &, int64_t); + MODBIND1RC(int64_t, font_get_weight, const RID &); + + MODBIND2(font_set_stretch, const RID &, int64_t); + MODBIND1RC(int64_t, font_get_stretch, const RID &); + MODBIND2(font_set_name, const RID &, const String &); MODBIND1RC(String, font_get_name, const RID &); @@ -589,6 +724,9 @@ public: MODBIND2(font_set_fixed_size, const RID &, int64_t); MODBIND1RC(int64_t, font_get_fixed_size, const RID &); + MODBIND2(font_set_allow_system_fallback, const RID &, bool); + MODBIND1RC(bool, font_is_allow_system_fallback, const RID &); + MODBIND2(font_set_force_autohinter, const RID &, bool); MODBIND1RC(bool, font_is_force_autohinter, const RID &); @@ -787,6 +925,8 @@ public: MODBIND2RC(String, string_to_upper, const String &, const String &); MODBIND2RC(String, string_to_lower, const String &, const String &); + MODBIND0(cleanup); + TextServerAdvanced(); ~TextServerAdvanced(); }; diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index aaef9c9a3d..9133c277bb 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -34,6 +34,7 @@ // Headers for building as GDExtension plug-in. #include <godot_cpp/classes/file_access.hpp> +#include <godot_cpp/classes/os.hpp> #include <godot_cpp/classes/project_settings.hpp> #include <godot_cpp/classes/rendering_server.hpp> #include <godot_cpp/classes/translation_server.hpp> @@ -49,6 +50,7 @@ using namespace godot; #include "core/config/project_settings.h" #include "core/error/error_macros.h" #include "core/string/print_string.h" +#include "core/string/translation.h" #include "core/string/ucaps.h" #include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. @@ -852,11 +854,13 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f if (fd->face->style_name != nullptr) { p_font_data->style_name = String::utf8((const char *)fd->face->style_name); } + p_font_data->weight = _font_get_weight_by_name(p_font_data->style_name.to_lower()); + p_font_data->stretch = _font_get_stretch_by_name(p_font_data->style_name.to_lower()); p_font_data->style_flags = 0; - if (fd->face->style_flags & FT_STYLE_FLAG_BOLD) { + if ((fd->face->style_flags & FT_STYLE_FLAG_BOLD) || p_font_data->weight >= 700) { p_font_data->style_flags.set_flag(FONT_BOLD); } - if (fd->face->style_flags & FT_STYLE_FLAG_ITALIC) { + if ((fd->face->style_flags & FT_STYLE_FLAG_ITALIC) || _is_ital_style(p_font_data->style_name.to_lower())) { p_font_data->style_flags.set_flag(FONT_ITALIC); } if (fd->face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) { @@ -1061,6 +1065,46 @@ String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const { return fd->style_name; } +void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weight) { + FontFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->weight = CLAMP(p_weight, 100, 999); +} + +int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const { + FontFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND_V(!fd, 400); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 400); + return fd->weight; +} + +void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stretch) { + FontFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->stretch = CLAMP(p_stretch, 50, 200); +} + +int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const { + FontFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND_V(!fd, 100); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 100); + return fd->stretch; +} + void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_name) { FontFallback *fd = font_owner.get_or_null(p_font_rid); ERR_FAIL_COND(!fd); @@ -1197,6 +1241,25 @@ int64_t TextServerFallback::_font_get_fixed_size(const RID &p_font_rid) const { return fd->fixed_size; } +void TextServerFallback::_font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) { + FontFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + if (fd->allow_system_fallback != p_allow_system_fallback) { + _font_clear_cache(fd); + fd->allow_system_fallback = p_allow_system_fallback; + } +} + +bool TextServerFallback::_font_is_allow_system_fallback(const RID &p_font_rid) const { + FontFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->allow_system_fallback; +} + void TextServerFallback::_font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) { FontFallback *fd = font_owner.get_or_null(p_font_rid); ERR_FAIL_COND(!fd); @@ -3603,6 +3666,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { sd->glyphs.push_back(gl); } else { // Text span. + RID prev_font; for (int j = span.start; j < span.end; j++) { Glyph gl; gl.start = j; @@ -3623,6 +3687,170 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { break; } } + if (!gl.font_rid.is_valid() && prev_font.is_valid()) { + if (_font_has_char(prev_font, gl.index)) { + gl.font_rid = prev_font; + } + } + if (!gl.font_rid.is_valid() && OS::get_singleton()->has_feature("system_fonts") && span.fonts.size() > 0) { + // Try system fallback. + RID fdef = span.fonts[0]; + if (_font_is_allow_system_fallback(fdef)) { + String text = sd->text.substr(j, 1); + String font_name = _font_get_name(fdef); + BitField<FontStyle> font_style = _font_get_style(fdef); + int font_weight = _font_get_weight(fdef); + int font_stretch = _font_get_stretch(fdef); + Dictionary dvar = _font_get_variation_coordinates(fdef); + static int64_t wgth_tag = _name_to_tag("weight"); + static int64_t wdth_tag = _name_to_tag("width"); + static int64_t ital_tag = _name_to_tag("italic"); + if (dvar.has(wgth_tag)) { + font_weight = dvar[wgth_tag].operator int(); + } + if (dvar.has(wdth_tag)) { + font_stretch = dvar[wdth_tag].operator int(); + } + if (dvar.has(ital_tag) && dvar[ital_tag].operator int() == 1) { + font_style.set_flag(TextServer::FONT_ITALIC); + } + + String locale = (span.language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : span.language; + + PackedStringArray fallback_font_name = OS::get_singleton()->get_system_font_path_for_text(font_name, text, locale, String(), font_weight, font_stretch, font_style & TextServer::FONT_ITALIC); +#ifdef GDEXTENSION + for (int fb = 0; fb < fallback_font_name.size(); fb++) { + const String &E = fallback_font_name[fb]; +#else + for (const String &E : fallback_font_name) { +#endif + SystemFontKey key = SystemFontKey(E, font_style & TextServer::FONT_ITALIC, font_weight, font_stretch, fdef, this); + if (system_fonts.has(key)) { + const SystemFontCache &sysf_cache = system_fonts[key]; + int best_score = 0; + int best_match = -1; + for (int face_idx = 0; face_idx < sysf_cache.var.size(); face_idx++) { + const SystemFontCacheRec &F = sysf_cache.var[face_idx]; + if (unlikely(!_font_has_char(F.rid, text[0]))) { + continue; + } + BitField<FontStyle> style = _font_get_style(F.rid); + int weight = _font_get_weight(F.rid); + int stretch = _font_get_stretch(F.rid); + int score = (20 - Math::abs(weight - font_weight) / 50); + score += (20 - Math::abs(stretch - font_stretch) / 10); + if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) { + score += 30; + } + if (score >= best_score) { + best_score = score; + best_match = face_idx; + } + if (best_score == 70) { + break; + } + } + if (best_match != -1) { + gl.font_rid = sysf_cache.var[best_match].rid; + } + } + if (!gl.font_rid.is_valid()) { + if (system_fonts.has(key)) { + const SystemFontCache &sysf_cache = system_fonts[key]; + if (sysf_cache.max_var == sysf_cache.var.size()) { + // All subfonts already tested, skip. + continue; + } + } + + if (!system_font_data.has(E)) { + system_font_data[E] = FileAccess::get_file_as_bytes(E); + } + + const PackedByteArray &font_data = system_font_data[E]; + + SystemFontCacheRec sysf; + sysf.rid = _create_font(); + _font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size()); + + Dictionary var = dvar; + // Select matching style from collection. + int best_score = 0; + int best_match = -1; + for (int face_idx = 0; face_idx < _font_get_face_count(sysf.rid); face_idx++) { + _font_set_face_index(sysf.rid, face_idx); + if (unlikely(!_font_has_char(sysf.rid, text[0]))) { + continue; + } + BitField<FontStyle> style = _font_get_style(sysf.rid); + int weight = _font_get_weight(sysf.rid); + int stretch = _font_get_stretch(sysf.rid); + int score = (20 - Math::abs(weight - font_weight) / 50); + score += (20 - Math::abs(stretch - font_stretch) / 10); + if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) { + score += 30; + } + if (score >= best_score) { + best_score = score; + best_match = face_idx; + } + if (best_score == 70) { + break; + } + } + if (best_match == -1) { + _free_rid(sysf.rid); + continue; + } else { + _font_set_face_index(sysf.rid, best_match); + } + sysf.index = best_match; + + // If it's a variable font, apply weight, stretch and italic coordinates to match requested style. + if (best_score != 70) { + Dictionary ftr = _font_supported_variation_list(sysf.rid); + if (ftr.has(wdth_tag)) { + var[wdth_tag] = font_stretch; + _font_set_stretch(sysf.rid, font_stretch); + } + if (ftr.has(wgth_tag)) { + var[wgth_tag] = font_weight; + _font_set_weight(sysf.rid, font_weight); + } + if ((font_style & TextServer::FONT_ITALIC) && ftr.has(ital_tag)) { + var[ital_tag] = 1; + _font_set_style(sysf.rid, _font_get_style(sysf.rid) | TextServer::FONT_ITALIC); + } + } + + _font_set_antialiasing(sysf.rid, key.antialiasing); + _font_set_generate_mipmaps(sysf.rid, key.mipmaps); + _font_set_multichannel_signed_distance_field(sysf.rid, key.msdf); + _font_set_msdf_pixel_range(sysf.rid, key.msdf_range); + _font_set_msdf_size(sysf.rid, key.msdf_source_size); + _font_set_fixed_size(sysf.rid, key.fixed_size); + _font_set_force_autohinter(sysf.rid, key.force_autohinter); + _font_set_hinting(sysf.rid, key.hinting); + _font_set_subpixel_positioning(sysf.rid, key.subpixel_positioning); + _font_set_variation_coordinates(sysf.rid, var); + _font_set_oversampling(sysf.rid, key.oversampling); + _font_set_embolden(sysf.rid, key.embolden); + _font_set_transform(sysf.rid, key.transform); + + if (system_fonts.has(key)) { + system_fonts[key].var.push_back(sysf); + } else { + SystemFontCache &sysf_cache = system_fonts[key]; + sysf_cache.max_var = _font_get_face_count(sysf.rid); + sysf_cache.var.push_back(sysf); + } + gl.font_rid = sysf.rid; + } + break; + } + } + } + prev_font = gl.font_rid; double scale = _font_get_scale(gl.font_rid, gl.font_size); if (gl.font_rid.is_valid()) { @@ -3893,6 +4121,17 @@ TextServerFallback::TextServerFallback() { _insert_feature_sets(); }; +void TextServerFallback::_cleanup() { + for (const KeyValue<SystemFontKey, SystemFontCache> &E : system_fonts) { + const Vector<SystemFontCacheRec> &sysf_cache = E.value.var; + for (const SystemFontCacheRec &F : sysf_cache) { + _free_rid(F.rid); + } + } + system_fonts.clear(); + system_font_data.clear(); +} + TextServerFallback::~TextServerFallback() { #ifdef MODULE_FREETYPE_ENABLED if (ft_library != nullptr) { diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index 7e0bc99618..f8a05f9433 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -256,6 +256,7 @@ class TextServerFallback : public TextServerExtension { int msdf_source_size = 48; int fixed_size = 0; bool force_autohinter = false; + bool allow_system_fallback = true; TextServer::Hinting hinting = TextServer::HINTING_LIGHT; TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; Dictionary variation_coordinates; @@ -266,6 +267,8 @@ class TextServerFallback : public TextServerExtension { BitField<TextServer::FontStyle> style_flags = 0; String font_name; String style_name; + int weight = 400; + int stretch = 100; HashMap<Vector2i, FontForSizeFallback *, VariantHasher, VariantComparator> cache; @@ -322,6 +325,58 @@ class TextServerFallback : public TextServerExtension { } } + _FORCE_INLINE_ int _font_get_weight_by_name(const String &p_sty_name) const { + String sty_name = p_sty_name.replace(" ", "").replace("-", ""); + if (sty_name.find("thin") >= 0 || sty_name.find("hairline") >= 0) { + return 100; + } else if (sty_name.find("extralight") >= 0 || sty_name.find("ultralight") >= 0) { + return 200; + } else if (sty_name.find("light") >= 0) { + return 300; + } else if (sty_name.find("semilight") >= 0) { + return 350; + } else if (sty_name.find("regular") >= 0) { + return 400; + } else if (sty_name.find("medium") >= 0) { + return 500; + } else if (sty_name.find("semibold") >= 0 || sty_name.find("demibold") >= 0) { + return 600; + } else if (sty_name.find("bold") >= 0) { + return 700; + } else if (sty_name.find("extrabold") >= 0 || sty_name.find("ultrabold") >= 0) { + return 800; + } else if (sty_name.find("black") >= 0 || sty_name.find("heavy") >= 0) { + return 900; + } else if (sty_name.find("extrablack") >= 0 || sty_name.find("ultrablack") >= 0) { + return 950; + } + return 400; + } + _FORCE_INLINE_ int _font_get_stretch_by_name(const String &p_sty_name) const { + String sty_name = p_sty_name.replace(" ", "").replace("-", ""); + if (sty_name.find("ultracondensed") >= 0) { + return 50; + } else if (sty_name.find("extracondensed") >= 0) { + return 63; + } else if (sty_name.find("condensed") >= 0) { + return 75; + } else if (sty_name.find("semicondensed") >= 0) { + return 87; + } else if (sty_name.find("semiexpanded") >= 0) { + return 113; + } else if (sty_name.find("expanded") >= 0) { + return 125; + } else if (sty_name.find("extraexpanded") >= 0) { + return 150; + } else if (sty_name.find("ultraexpanded") >= 0) { + return 200; + } + return 100; + } + _FORCE_INLINE_ bool _is_ital_style(const String &p_sty_name) const { + return (p_sty_name.find("italic") >= 0) || (p_sty_name.find("oblique") >= 0); + } + // Shaped text cache data. struct TrimData { int trim_pos = -1; @@ -398,6 +453,81 @@ class TextServerFallback : public TextServerExtension { mutable RID_PtrOwner<FontFallback> font_owner; mutable RID_PtrOwner<ShapedTextDataFallback> shaped_owner; + struct SystemFontKey { + String font_name; + TextServer::FontAntialiasing antialiasing = TextServer::FONT_ANTIALIASING_GRAY; + bool italic = false; + bool mipmaps = false; + bool msdf = false; + bool force_autohinter = false; + int weight = 400; + int stretch = 100; + int msdf_range = 14; + int msdf_source_size = 48; + int fixed_size = 0; + TextServer::Hinting hinting = TextServer::HINTING_LIGHT; + TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; + Dictionary variation_coordinates; + double oversampling = 0.0; + double embolden = 0.0; + Transform2D transform; + + bool operator==(const SystemFontKey &p_b) const { + return (font_name == p_b.font_name) && (antialiasing == p_b.antialiasing) && (italic == p_b.italic) && (mipmaps == p_b.mipmaps) && (msdf == p_b.msdf) && (force_autohinter == p_b.force_autohinter) && (weight == p_b.weight) && (stretch == p_b.stretch) && (msdf_range == p_b.msdf_range) && (msdf_source_size == p_b.msdf_source_size) && (fixed_size == p_b.fixed_size) && (hinting == p_b.hinting) && (subpixel_positioning == p_b.subpixel_positioning) && (variation_coordinates == p_b.variation_coordinates) && (oversampling == p_b.oversampling) && (embolden == p_b.embolden) && (transform == p_b.transform); + } + + SystemFontKey(const String &p_font_name, bool p_italic, int p_weight, int p_stretch, RID p_font, const TextServerFallback *p_fb) { + font_name = p_font_name; + italic = p_italic; + weight = p_weight; + stretch = p_stretch; + antialiasing = p_fb->_font_get_antialiasing(p_font); + mipmaps = p_fb->_font_get_generate_mipmaps(p_font); + msdf = p_fb->_font_is_multichannel_signed_distance_field(p_font); + msdf_range = p_fb->_font_get_msdf_pixel_range(p_font); + msdf_source_size = p_fb->_font_get_msdf_size(p_font); + fixed_size = p_fb->_font_get_fixed_size(p_font); + force_autohinter = p_fb->_font_is_force_autohinter(p_font); + hinting = p_fb->_font_get_hinting(p_font); + subpixel_positioning = p_fb->_font_get_subpixel_positioning(p_font); + variation_coordinates = p_fb->_font_get_variation_coordinates(p_font); + oversampling = p_fb->_font_get_oversampling(p_font); + embolden = p_fb->_font_get_embolden(p_font); + transform = p_fb->_font_get_transform(p_font); + } + }; + + struct SystemFontCacheRec { + RID rid; + int index = 0; + }; + + struct SystemFontCache { + Vector<SystemFontCacheRec> var; + int max_var = 0; + }; + + struct SystemFontKeyHasher { + _FORCE_INLINE_ static uint32_t hash(const SystemFontKey &p_a) { + uint32_t hash = p_a.font_name.hash(); + hash = hash_murmur3_one_32(p_a.variation_coordinates.hash(), hash); + hash = hash_murmur3_one_32(p_a.weight, hash); + hash = hash_murmur3_one_32(p_a.stretch, hash); + hash = hash_murmur3_one_32(p_a.msdf_range, hash); + hash = hash_murmur3_one_32(p_a.msdf_source_size, hash); + hash = hash_murmur3_one_32(p_a.fixed_size, hash); + hash = hash_murmur3_one_double(p_a.oversampling, hash); + hash = hash_murmur3_one_double(p_a.embolden, hash); + hash = hash_murmur3_one_real(p_a.transform[0].x, hash); + hash = hash_murmur3_one_real(p_a.transform[0].y, hash); + hash = hash_murmur3_one_real(p_a.transform[1].x, hash); + hash = hash_murmur3_one_real(p_a.transform[1].y, hash); + return hash_fmix32(hash_murmur3_one_32(((int)p_a.mipmaps) | ((int)p_a.msdf << 1) | ((int)p_a.italic << 2) | ((int)p_a.force_autohinter << 3) | ((int)p_a.hinting << 4) | ((int)p_a.subpixel_positioning << 8) | ((int)p_a.antialiasing << 12), hash)); + } + }; + mutable HashMap<SystemFontKey, SystemFontCache, SystemFontKeyHasher> system_fonts; + mutable HashMap<String, PackedByteArray> system_font_data; + void _realign(ShapedTextDataFallback *p_sd) const; protected: @@ -442,6 +572,12 @@ public: MODBIND2(font_set_style_name, const RID &, const String &); MODBIND1RC(String, font_get_style_name, const RID &); + MODBIND2(font_set_weight, const RID &, int64_t); + MODBIND1RC(int64_t, font_get_weight, const RID &); + + MODBIND2(font_set_stretch, const RID &, int64_t); + MODBIND1RC(int64_t, font_get_stretch, const RID &); + MODBIND2(font_set_name, const RID &, const String &); MODBIND1RC(String, font_get_name, const RID &); @@ -463,6 +599,9 @@ public: MODBIND2(font_set_fixed_size, const RID &, int64_t); MODBIND1RC(int64_t, font_get_fixed_size, const RID &); + MODBIND2(font_set_allow_system_fallback, const RID &, bool); + MODBIND1RC(bool, font_is_allow_system_fallback, const RID &); + MODBIND2(font_set_force_autohinter, const RID &, bool); MODBIND1RC(bool, font_is_force_autohinter, const RID &); @@ -651,6 +790,8 @@ public: MODBIND2RC(String, string_to_upper, const String &, const String &); MODBIND2RC(String, string_to_lower, const String &, const String &); + MODBIND0(cleanup); + TextServerFallback(); ~TextServerFallback(); }; diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index f76f3844e9..9c796a8c07 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -221,7 +221,7 @@ float DisplayServerAndroid::screen_get_refresh_rate(int p_screen) const { return godot_io_java->get_screen_refresh_rate(SCREEN_REFRESH_RATE_FALLBACK); } -bool DisplayServerAndroid::screen_is_touchscreen(int p_screen) const { +bool DisplayServerAndroid::is_touchscreen_available() const { return true; } @@ -316,9 +316,6 @@ DisplayServer::WindowID DisplayServerAndroid::get_window_at_screen_position(cons int64_t DisplayServerAndroid::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0); switch (p_handle_type) { - case DISPLAY_HANDLE: { - return 0; // Not supported. - } case WINDOW_HANDLE: { return reinterpret_cast<int64_t>(static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity()); } @@ -326,8 +323,17 @@ int64_t DisplayServerAndroid::window_get_native_handle(HandleType p_handle_type, return 0; // Not supported. } #ifdef GLES3_ENABLED + case DISPLAY_HANDLE: { + if (rendering_driver == "opengl3") { + return reinterpret_cast<int64_t>(eglGetCurrentDisplay()); + } + return 0; + } case OPENGL_CONTEXT: { - return reinterpret_cast<int64_t>(eglGetCurrentContext()); + if (rendering_driver == "opengl3") { + return reinterpret_cast<int64_t>(eglGetCurrentContext()); + } + return 0; } #endif default: { @@ -360,6 +366,10 @@ Point2i DisplayServerAndroid::window_get_position(DisplayServer::WindowID p_wind return Point2i(); } +Point2i DisplayServerAndroid::window_get_position_with_decorations(DisplayServer::WindowID p_window) const { + return Point2i(); +} + void DisplayServerAndroid::window_set_position(const Point2i &p_position, DisplayServer::WindowID p_window) { // Not supported on Android. } @@ -392,7 +402,7 @@ Size2i DisplayServerAndroid::window_get_size(DisplayServer::WindowID p_window) c return OS_Android::get_singleton()->get_display_size(); } -Size2i DisplayServerAndroid::window_get_real_size(DisplayServer::WindowID p_window) const { +Size2i DisplayServerAndroid::window_get_size_with_decorations(DisplayServer::WindowID p_window) const { return OS_Android::get_singleton()->get_display_size(); } diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index c7f4d8046f..9ed07ca971 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -121,7 +121,7 @@ public: virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; - virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual bool is_touchscreen_available() const override; virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), VirtualKeyboardType p_type = KEYBOARD_TYPE_DEFAULT, int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1) override; virtual void virtual_keyboard_hide() override; @@ -150,6 +150,7 @@ public: virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; @@ -162,7 +163,7 @@ public: virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 737e25b270..795a542ed5 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -703,7 +703,7 @@ Error EditorExportPlatformAndroid::save_apk_so(void *p_userdata, const SharedObj exported = true; String abi = abis[abi_index].abi; String dst_path = String("lib").path_join(abi).path_join(p_so.path.get_file()); - Vector<uint8_t> array = FileAccess::get_file_as_array(p_so.path); + Vector<uint8_t> array = FileAccess::get_file_as_bytes(p_so.path); Error store_err = store_in_apk(ed, dst_path, array); ERR_FAIL_COND_V_MSG(store_err, store_err, "Cannot store in apk file '" + dst_path + "'."); } @@ -748,7 +748,7 @@ Error EditorExportPlatformAndroid::copy_gradle_so(void *p_userdata, const Shared String abi = abis[abi_index].abi; String filename = p_so.path.get_file(); String dst_path = base.path_join(type).path_join(abi).path_join(filename); - Vector<uint8_t> data = FileAccess::get_file_as_array(p_so.path); + Vector<uint8_t> data = FileAccess::get_file_as_bytes(p_so.path); print_verbose("Copying .so file from " + p_so.path + " to " + dst_path); Error err = store_file_at_path(dst_path, data); ERR_FAIL_COND_V_MSG(err, err, "Failed to copy .so file from " + p_so.path + " to " + dst_path); diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt b/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt index c9282dd247..1a3576a6a9 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/StorageScope.kt @@ -90,6 +90,11 @@ internal enum class StorageScope { return APP } + var rootDir: String? = System.getenv("ANDROID_ROOT") + if (rootDir != null && canonicalPathFile.startsWith(rootDir)) { + return APP + } + if (sharedDir != null && canonicalPathFile.startsWith(sharedDir)) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { // Before R, apps had access to shared storage so long as they have the right diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 4f81e4bccd..317a63f21f 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -337,6 +337,229 @@ String OS_Android::get_data_path() const { return get_user_data_dir(); } +void OS_Android::_load_system_font_config() { + font_aliases.clear(); + fonts.clear(); + font_names.clear(); + + Ref<XMLParser> parser; + parser.instantiate(); + + Error err = parser->open(String(getenv("ANDROID_ROOT")).path_join("/etc/fonts.xml")); + if (err == OK) { + bool in_font_node = false; + String fb, fn; + FontInfo fi; + + while (parser->read() == OK) { + if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { + in_font_node = false; + if (parser->get_node_name() == "familyset") { + int ver = parser->has_attribute("version") ? parser->get_attribute_value("version").to_int() : 0; + if (ver < 21) { + ERR_PRINT(vformat("Unsupported font config version %s", ver)); + break; + } + } else if (parser->get_node_name() == "alias") { + String name = parser->has_attribute("name") ? parser->get_attribute_value("name").strip_edges() : String(); + String to = parser->has_attribute("to") ? parser->get_attribute_value("to").strip_edges() : String(); + if (!name.is_empty() && !to.is_empty()) { + font_aliases[name] = to; + } + } else if (parser->get_node_name() == "family") { + fn = parser->has_attribute("name") ? parser->get_attribute_value("name").strip_edges() : String(); + String lang_code = parser->has_attribute("lang") ? parser->get_attribute_value("lang").strip_edges() : String(); + Vector<String> lang_codes = lang_code.split(","); + for (int i = 0; i < lang_codes.size(); i++) { + Vector<String> lang_code_elements = lang_codes[i].split("-"); + if (lang_code_elements.size() >= 1 && lang_code_elements[0] != "und") { + // Add missing script codes. + if (lang_code_elements[0] == "ko") { + fi.script.insert("Hani"); + fi.script.insert("Hang"); + } + if (lang_code_elements[0] == "ja") { + fi.script.insert("Hani"); + fi.script.insert("Kana"); + fi.script.insert("Hira"); + } + if (!lang_code_elements[0].is_empty()) { + fi.lang.insert(lang_code_elements[0]); + } + } + if (lang_code_elements.size() >= 2) { + // Add common codes for variants and remove variants not supported by HarfBuzz/ICU. + if (lang_code_elements[1] == "Aran") { + fi.script.insert("Arab"); + } + if (lang_code_elements[1] == "Cyrs") { + fi.script.insert("Cyrl"); + } + if (lang_code_elements[1] == "Hanb") { + fi.script.insert("Hani"); + fi.script.insert("Bopo"); + } + if (lang_code_elements[1] == "Hans" || lang_code_elements[1] == "Hant") { + fi.script.insert("Hani"); + } + if (lang_code_elements[1] == "Syrj" || lang_code_elements[1] == "Syre" || lang_code_elements[1] == "Syrn") { + fi.script.insert("Syrc"); + } + if (!lang_code_elements[1].is_empty() && lang_code_elements[1] != "Zsym" && lang_code_elements[1] != "Zsye" && lang_code_elements[1] != "Zmth") { + fi.script.insert(lang_code_elements[1]); + } + } + } + } else if (parser->get_node_name() == "font") { + in_font_node = true; + fb = parser->has_attribute("fallbackFor") ? parser->get_attribute_value("fallbackFor").strip_edges() : String(); + fi.weight = parser->has_attribute("weight") ? parser->get_attribute_value("weight").to_int() : 400; + fi.italic = parser->has_attribute("style") && parser->get_attribute_value("style").strip_edges() == "italic"; + } + } + if (parser->get_node_type() == XMLParser::NODE_TEXT) { + if (in_font_node) { + fi.filename = parser->get_node_data().strip_edges(); + fi.font_name = fn; + if (!fb.is_empty() && fn.is_empty()) { + fi.font_name = fb; + fi.priority = 2; + } + if (fi.font_name.is_empty()) { + fi.font_name = "sans-serif"; + fi.priority = 5; + } + if (fi.font_name.ends_with("-condensed")) { + fi.stretch = 75; + fi.font_name = fi.font_name.trim_suffix("-condensed"); + } + fonts.push_back(fi); + font_names.insert(fi.font_name); + } + } + if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) { + in_font_node = false; + if (parser->get_node_name() == "font") { + fb = String(); + fi.font_name = String(); + fi.priority = 0; + fi.weight = 400; + fi.stretch = 100; + fi.italic = false; + } else if (parser->get_node_name() == "family") { + fi = FontInfo(); + fn = String(); + } + } + } + parser->close(); + } else { + ERR_PRINT("Unable to load font config"); + } + + font_config_loaded = true; +} + +Vector<String> OS_Android::get_system_fonts() const { + if (!font_config_loaded) { + const_cast<OS_Android *>(this)->_load_system_font_config(); + } + Vector<String> ret; + for (const String &E : font_names) { + ret.push_back(E); + } + return ret; +} + +Vector<String> OS_Android::get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale, const String &p_script, int p_weight, int p_stretch, bool p_italic) const { + if (!font_config_loaded) { + const_cast<OS_Android *>(this)->_load_system_font_config(); + } + String font_name = p_font_name.to_lower(); + if (font_aliases.has(font_name)) { + font_name = font_aliases[font_name]; + } + String root = String(getenv("ANDROID_ROOT")).path_join("fonts"); + String lang_prefix = p_locale.split("_")[0]; + Vector<String> ret; + int best_score = 0; + for (const List<FontInfo>::Element *E = fonts.front(); E; E = E->next()) { + int score = 0; + if (!E->get().script.is_empty() && !p_script.is_empty() && !E->get().script.has(p_script)) { + continue; + } + float sim = E->get().font_name.similarity(font_name); + if (sim > 0.0) { + score += (60 * sim + 5 - E->get().priority); + } + if (E->get().lang.has(p_locale)) { + score += 120; + } else if (E->get().lang.has(lang_prefix)) { + score += 115; + } + if (E->get().script.has(p_script)) { + score += 240; + } + score += (20 - Math::abs(E->get().weight - p_weight) / 50); + score += (20 - Math::abs(E->get().stretch - p_stretch) / 10); + if (E->get().italic == p_italic) { + score += 30; + } + if (score > best_score) { + best_score = score; + if (ret.find(root.path_join(E->get().filename)) < 0) { + ret.insert(0, root.path_join(E->get().filename)); + } + } else if (score == best_score || E->get().script.is_empty()) { + if (ret.find(root.path_join(E->get().filename)) < 0) { + ret.push_back(root.path_join(E->get().filename)); + } + } + if (score >= 490) { + break; // Perfect match. + } + } + + return ret; +} + +String OS_Android::get_system_font_path(const String &p_font_name, int p_weight, int p_stretch, bool p_italic) const { + if (!font_config_loaded) { + const_cast<OS_Android *>(this)->_load_system_font_config(); + } + String font_name = p_font_name.to_lower(); + if (font_aliases.has(font_name)) { + font_name = font_aliases[font_name]; + } + String root = String(getenv("ANDROID_ROOT")).path_join("fonts"); + + int best_score = 0; + const List<FontInfo>::Element *best_match = nullptr; + + for (const List<FontInfo>::Element *E = fonts.front(); E; E = E->next()) { + int score = 0; + if (E->get().font_name == font_name) { + score += (65 - E->get().priority); + } + score += (20 - Math::abs(E->get().weight - p_weight) / 50); + score += (20 - Math::abs(E->get().stretch - p_stretch) / 10); + if (E->get().italic == p_italic) { + score += 30; + } + if (score >= 60 && score > best_score) { + best_score = score; + best_match = E; + } + if (score >= 140) { + break; // Perfect match. + } + } + if (best_match) { + return root.path_join(best_match->get().filename); + } + return String(); +} + String OS_Android::get_executable_path() const { // Since unix process creation is restricted on Android, we bypass // OS_Unix::get_executable_path() so we can return ANDROID_EXEC_PATH. @@ -449,6 +672,9 @@ String OS_Android::get_config_path() const { } bool OS_Android::_check_internal_feature_support(const String &p_feature) { + if (p_feature == "system_fonts") { + return true; + } if (p_feature == "mobile") { return true; } diff --git a/platform/android/os_android.h b/platform/android/os_android.h index d6546a3507..9034615fc4 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -62,9 +62,26 @@ private: MainLoop *main_loop = nullptr; + struct FontInfo { + String font_name; + HashSet<String> lang; + HashSet<String> script; + int weight = 400; + int stretch = 100; + bool italic = false; + int priority = 0; + String filename; + }; + + HashMap<String, String> font_aliases; + List<FontInfo> fonts; + HashSet<String> font_names; + bool font_config_loaded = false; + GodotJavaWrapper *godot_java = nullptr; GodotIOJavaWrapper *godot_io_java = nullptr; + void _load_system_font_config(); String get_system_property(const char *key) const; public: @@ -114,6 +131,10 @@ public: ANativeWindow *get_native_window() const; virtual Error shell_open(String p_uri) override; + + virtual Vector<String> get_system_fonts() const override; + virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; + virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; virtual String get_executable_path() const override; virtual String get_user_data_dir() const override; virtual String get_data_path() const override; diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h index 447f919139..4901eeefaf 100644 --- a/platform/ios/display_server_ios.h +++ b/platform/ios/display_server_ios.h @@ -162,6 +162,7 @@ public: virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; @@ -174,7 +175,7 @@ public: virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; @@ -199,7 +200,7 @@ public: virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override; - virtual bool screen_is_touchscreen(int p_screen) const override; + virtual bool is_touchscreen_available() const override; virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, VirtualKeyboardType p_type, int p_max_length, int p_cursor_start, int p_cursor_end) override; virtual void virtual_keyboard_hide() override; diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm index 6793b40dd4..ed1bc499ed 100644 --- a/platform/ios/display_server_ios.mm +++ b/platform/ios/display_server_ios.mm @@ -496,6 +496,10 @@ Point2i DisplayServerIOS::window_get_position(WindowID p_window) const { return Point2i(); } +Point2i DisplayServerIOS::window_get_position_with_decorations(WindowID p_window) const { + return Point2i(); +} + void DisplayServerIOS::window_set_position(const Point2i &p_position, WindowID p_window) { // Probably not supported for single window iOS app } @@ -529,7 +533,7 @@ Size2i DisplayServerIOS::window_get_size(WindowID p_window) const { return Size2i(screenBounds.size.width, screenBounds.size.height) * screen_get_max_scale(); } -Size2i DisplayServerIOS::window_get_real_size(WindowID p_window) const { +Size2i DisplayServerIOS::window_get_size_with_decorations(WindowID p_window) const { return window_get_size(p_window); } @@ -581,7 +585,7 @@ bool DisplayServerIOS::can_any_window_draw() const { return true; } -bool DisplayServerIOS::screen_is_touchscreen(int p_screen) const { +bool DisplayServerIOS::is_touchscreen_available() const { return true; } diff --git a/platform/ios/os_ios.h b/platform/ios/os_ios.h index 3560de7486..186efd14a8 100644 --- a/platform/ios/os_ios.h +++ b/platform/ios/os_ios.h @@ -73,6 +73,10 @@ private: bool is_focused = false; + CGFloat _weight_to_ct(int p_weight) const; + CGFloat _stretch_to_ct(int p_stretch) const; + String _get_default_fontname(const String &p_font_name) const; + void deinitialize_modules(); public: @@ -90,7 +94,8 @@ public: virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; virtual Vector<String> get_system_fonts() const override; - virtual String get_system_font_path(const String &p_font_name, bool p_bold = false, bool p_italic = false) const override; + virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; + virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override; virtual Error close_dynamic_library(void *p_library_handle) override; diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm index 9a8cfc2593..9617627b6f 100644 --- a/platform/ios/os_ios.mm +++ b/platform/ios/os_ios.mm @@ -334,9 +334,7 @@ Vector<String> OS_IOS::get_system_fonts() const { return ret; } -String OS_IOS::get_system_font_path(const String &p_font_name, bool p_bold, bool p_italic) const { - String ret; - +String OS_IOS::_get_default_fontname(const String &p_font_name) const { String font_name = p_font_name; if (font_name.to_lower() == "sans-serif") { font_name = "Helvetica"; @@ -349,21 +347,153 @@ String OS_IOS::get_system_font_path(const String &p_font_name, bool p_bold, bool } else if (font_name.to_lower() == "cursive") { font_name = "Apple Chancery"; }; + return font_name; +} + +CGFloat OS_IOS::_weight_to_ct(int p_weight) const { + if (p_weight < 150) { + return -0.80; + } else if (p_weight < 250) { + return -0.60; + } else if (p_weight < 350) { + return -0.40; + } else if (p_weight < 450) { + return 0.0; + } else if (p_weight < 550) { + return 0.23; + } else if (p_weight < 650) { + return 0.30; + } else if (p_weight < 750) { + return 0.40; + } else if (p_weight < 850) { + return 0.56; + } else if (p_weight < 925) { + return 0.62; + } else { + return 1.00; + } +} + +CGFloat OS_IOS::_stretch_to_ct(int p_stretch) const { + if (p_stretch < 56) { + return -0.5; + } else if (p_stretch < 69) { + return -0.37; + } else if (p_stretch < 81) { + return -0.25; + } else if (p_stretch < 93) { + return -0.13; + } else if (p_stretch < 106) { + return 0.0; + } else if (p_stretch < 137) { + return 0.13; + } else if (p_stretch < 144) { + return 0.25; + } else if (p_stretch < 162) { + return 0.37; + } else { + return 0.5; + } +} + +Vector<String> OS_IOS::get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale, const String &p_script, int p_weight, int p_stretch, bool p_italic) const { + Vector<String> ret; + String font_name = _get_default_fontname(p_font_name); + + CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8); + CTFontSymbolicTraits traits = 0; + if (p_weight >= 700) { + traits |= kCTFontBoldTrait; + } + if (p_italic) { + traits |= kCTFontItalicTrait; + } + if (p_stretch < 100) { + traits |= kCTFontCondensedTrait; + } else if (p_stretch > 100) { + traits |= kCTFontExpandedTrait; + } + + CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits); + CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); + CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits); + + CGFloat weight = _weight_to_ct(p_weight); + CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight); + CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight); + + CGFloat stretch = _stretch_to_ct(p_stretch); + CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch); + CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch); + + CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); + CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name); + CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict); + + CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes); + if (font) { + CTFontRef family = CTFontCreateWithFontDescriptor(font, 0, nullptr); + CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, p_text.utf8().get_data(), kCFStringEncodingUTF8); + CFRange range = CFRangeMake(0, CFStringGetLength(string)); + CTFontRef fallback_family = CTFontCreateForString(family, string, range); + if (fallback_family) { + CTFontDescriptorRef fallback_font = CTFontCopyFontDescriptor(fallback_family); + if (fallback_font) { + CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fallback_font, kCTFontURLAttribute); + if (url) { + NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]]; + ret.push_back(String::utf8([font_path UTF8String])); + CFRelease(url); + } + CFRelease(fallback_font); + } + CFRelease(fallback_family); + } + CFRelease(string); + CFRelease(font); + } + + CFRelease(attributes); + CFRelease(traits_dict); + CFRelease(sym_traits); + CFRelease(font_stretch); + CFRelease(font_weight); + CFRelease(name); + + return ret; +} + +String OS_IOS::get_system_font_path(const String &p_font_name, int p_weight, int p_stretch, bool p_italic) const { + String ret; + String font_name = _get_default_fontname(p_font_name); CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8); CTFontSymbolicTraits traits = 0; - if (p_bold) { + if (p_weight >= 700) { traits |= kCTFontBoldTrait; } if (p_italic) { traits |= kCTFontItalicTrait; } + if (p_stretch < 100) { + traits |= kCTFontCondensedTrait; + } else if (p_stretch > 100) { + traits |= kCTFontExpandedTrait; + } CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits); CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits); + CGFloat weight = _weight_to_ct(p_weight); + CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight); + CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight); + + CGFloat stretch = _stretch_to_ct(p_stretch); + CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch); + CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch); + CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name); CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict); @@ -382,6 +512,8 @@ String OS_IOS::get_system_font_path(const String &p_font_name, bool p_bold, bool CFRelease(attributes); CFRelease(traits_dict); CFRelease(sym_traits); + CFRelease(font_stretch); + CFRelease(font_weight); CFRelease(name); return ret; diff --git a/platform/linuxbsd/fontconfig-so_wrap.c b/platform/linuxbsd/fontconfig-so_wrap.c index 1a915faf98..a428cf1fb4 100644 --- a/platform/linuxbsd/fontconfig-so_wrap.c +++ b/platform/linuxbsd/fontconfig-so_wrap.c @@ -1,7 +1,7 @@ // This file is generated. Do not edit! // see https://github.com/hpvb/dynload-wrapper for details -// generated by ./generate-wrapper.py 0.3 on 2022-07-29 05:40:07 -// flags: ./generate-wrapper.py --include /usr/include/fontconfig/fontconfig.h --sys-include <fontconfig/fontconfig.h> --soname libfontconfig.so --init-name fontconfig --output-header fontconfig-so_wrap.h --output-implementation fontconfig-so_wrap.c --omit-prefix FcCharSet +// generated by ./generate-wrapper.py 0.3 on 2022-11-22 10:28:00 +// flags: ./generate-wrapper.py --include /usr/include/fontconfig/fontconfig.h --sys-include <fontconfig/fontconfig.h> --soname libfontconfig.so --init-name fontconfig --output-header fontconfig-so_wrap.h --output-implementation fontconfig-so_wrap.c --omit-prefix FcCharSetFirst --omit-prefix FcCharSetNext // #include <stdint.h> @@ -18,6 +18,8 @@ #define FcDirCacheValid FcDirCacheValid_dylibloader_orig_fontconfig #define FcDirCacheClean FcDirCacheClean_dylibloader_orig_fontconfig #define FcCacheCreateTagFile FcCacheCreateTagFile_dylibloader_orig_fontconfig +#define FcDirCacheCreateUUID FcDirCacheCreateUUID_dylibloader_orig_fontconfig +#define FcDirCacheDeleteUUID FcDirCacheDeleteUUID_dylibloader_orig_fontconfig #define FcConfigHome FcConfigHome_dylibloader_orig_fontconfig #define FcConfigEnableHome FcConfigEnableHome_dylibloader_orig_fontconfig #define FcConfigFilename FcConfigFilename_dylibloader_orig_fontconfig @@ -44,6 +46,26 @@ #define FcConfigSubstitute FcConfigSubstitute_dylibloader_orig_fontconfig #define FcConfigGetSysRoot FcConfigGetSysRoot_dylibloader_orig_fontconfig #define FcConfigSetSysRoot FcConfigSetSysRoot_dylibloader_orig_fontconfig +#define FcConfigFileInfoIterInit FcConfigFileInfoIterInit_dylibloader_orig_fontconfig +#define FcConfigFileInfoIterNext FcConfigFileInfoIterNext_dylibloader_orig_fontconfig +#define FcConfigFileInfoIterGet FcConfigFileInfoIterGet_dylibloader_orig_fontconfig +#define FcCharSetCreate FcCharSetCreate_dylibloader_orig_fontconfig +#define FcCharSetNew FcCharSetNew_dylibloader_orig_fontconfig +#define FcCharSetDestroy FcCharSetDestroy_dylibloader_orig_fontconfig +#define FcCharSetAddChar FcCharSetAddChar_dylibloader_orig_fontconfig +#define FcCharSetDelChar FcCharSetDelChar_dylibloader_orig_fontconfig +#define FcCharSetCopy FcCharSetCopy_dylibloader_orig_fontconfig +#define FcCharSetEqual FcCharSetEqual_dylibloader_orig_fontconfig +#define FcCharSetIntersect FcCharSetIntersect_dylibloader_orig_fontconfig +#define FcCharSetUnion FcCharSetUnion_dylibloader_orig_fontconfig +#define FcCharSetSubtract FcCharSetSubtract_dylibloader_orig_fontconfig +#define FcCharSetMerge FcCharSetMerge_dylibloader_orig_fontconfig +#define FcCharSetHasChar FcCharSetHasChar_dylibloader_orig_fontconfig +#define FcCharSetCount FcCharSetCount_dylibloader_orig_fontconfig +#define FcCharSetIntersectCount FcCharSetIntersectCount_dylibloader_orig_fontconfig +#define FcCharSetSubtractCount FcCharSetSubtractCount_dylibloader_orig_fontconfig +#define FcCharSetIsSubset FcCharSetIsSubset_dylibloader_orig_fontconfig +#define FcCharSetCoverage FcCharSetCoverage_dylibloader_orig_fontconfig #define FcValuePrint FcValuePrint_dylibloader_orig_fontconfig #define FcPatternPrint FcPatternPrint_dylibloader_orig_fontconfig #define FcFontSetPrint FcFontSetPrint_dylibloader_orig_fontconfig @@ -59,6 +81,7 @@ #define FcDirCacheLoadFile FcDirCacheLoadFile_dylibloader_orig_fontconfig #define FcDirCacheUnload FcDirCacheUnload_dylibloader_orig_fontconfig #define FcFreeTypeQuery FcFreeTypeQuery_dylibloader_orig_fontconfig +#define FcFreeTypeQueryAll FcFreeTypeQueryAll_dylibloader_orig_fontconfig #define FcFontSetCreate FcFontSetCreate_dylibloader_orig_fontconfig #define FcFontSetDestroy FcFontSetDestroy_dylibloader_orig_fontconfig #define FcFontSetAdd FcFontSetAdd_dylibloader_orig_fontconfig @@ -129,6 +152,7 @@ #define FcValueEqual FcValueEqual_dylibloader_orig_fontconfig #define FcValueSave FcValueSave_dylibloader_orig_fontconfig #define FcPatternDestroy FcPatternDestroy_dylibloader_orig_fontconfig +#define FcPatternObjectCount FcPatternObjectCount_dylibloader_orig_fontconfig #define FcPatternEqual FcPatternEqual_dylibloader_orig_fontconfig #define FcPatternEqualSubset FcPatternEqualSubset_dylibloader_orig_fontconfig #define FcPatternHash FcPatternHash_dylibloader_orig_fontconfig @@ -162,8 +186,18 @@ #define FcRangeDestroy FcRangeDestroy_dylibloader_orig_fontconfig #define FcRangeCopy FcRangeCopy_dylibloader_orig_fontconfig #define FcRangeGetDouble FcRangeGetDouble_dylibloader_orig_fontconfig +#define FcPatternIterStart FcPatternIterStart_dylibloader_orig_fontconfig +#define FcPatternIterNext FcPatternIterNext_dylibloader_orig_fontconfig +#define FcPatternIterEqual FcPatternIterEqual_dylibloader_orig_fontconfig +#define FcPatternFindIter FcPatternFindIter_dylibloader_orig_fontconfig +#define FcPatternIterIsValid FcPatternIterIsValid_dylibloader_orig_fontconfig +#define FcPatternIterGetObject FcPatternIterGetObject_dylibloader_orig_fontconfig +#define FcPatternIterValueCount FcPatternIterValueCount_dylibloader_orig_fontconfig +#define FcPatternIterGetValue FcPatternIterGetValue_dylibloader_orig_fontconfig #define FcWeightFromOpenType FcWeightFromOpenType_dylibloader_orig_fontconfig +#define FcWeightFromOpenTypeDouble FcWeightFromOpenTypeDouble_dylibloader_orig_fontconfig #define FcWeightToOpenType FcWeightToOpenType_dylibloader_orig_fontconfig +#define FcWeightToOpenTypeDouble FcWeightToOpenTypeDouble_dylibloader_orig_fontconfig #define FcStrCopy FcStrCopy_dylibloader_orig_fontconfig #define FcStrCopyFilename FcStrCopyFilename_dylibloader_orig_fontconfig #define FcStrPlus FcStrPlus_dylibloader_orig_fontconfig @@ -207,6 +241,8 @@ #undef FcDirCacheValid #undef FcDirCacheClean #undef FcCacheCreateTagFile +#undef FcDirCacheCreateUUID +#undef FcDirCacheDeleteUUID #undef FcConfigHome #undef FcConfigEnableHome #undef FcConfigFilename @@ -233,6 +269,26 @@ #undef FcConfigSubstitute #undef FcConfigGetSysRoot #undef FcConfigSetSysRoot +#undef FcConfigFileInfoIterInit +#undef FcConfigFileInfoIterNext +#undef FcConfigFileInfoIterGet +#undef FcCharSetCreate +#undef FcCharSetNew +#undef FcCharSetDestroy +#undef FcCharSetAddChar +#undef FcCharSetDelChar +#undef FcCharSetCopy +#undef FcCharSetEqual +#undef FcCharSetIntersect +#undef FcCharSetUnion +#undef FcCharSetSubtract +#undef FcCharSetMerge +#undef FcCharSetHasChar +#undef FcCharSetCount +#undef FcCharSetIntersectCount +#undef FcCharSetSubtractCount +#undef FcCharSetIsSubset +#undef FcCharSetCoverage #undef FcValuePrint #undef FcPatternPrint #undef FcFontSetPrint @@ -248,6 +304,7 @@ #undef FcDirCacheLoadFile #undef FcDirCacheUnload #undef FcFreeTypeQuery +#undef FcFreeTypeQueryAll #undef FcFontSetCreate #undef FcFontSetDestroy #undef FcFontSetAdd @@ -318,6 +375,7 @@ #undef FcValueEqual #undef FcValueSave #undef FcPatternDestroy +#undef FcPatternObjectCount #undef FcPatternEqual #undef FcPatternEqualSubset #undef FcPatternHash @@ -351,8 +409,18 @@ #undef FcRangeDestroy #undef FcRangeCopy #undef FcRangeGetDouble +#undef FcPatternIterStart +#undef FcPatternIterNext +#undef FcPatternIterEqual +#undef FcPatternFindIter +#undef FcPatternIterIsValid +#undef FcPatternIterGetObject +#undef FcPatternIterValueCount +#undef FcPatternIterGetValue #undef FcWeightFromOpenType +#undef FcWeightFromOpenTypeDouble #undef FcWeightToOpenType +#undef FcWeightToOpenTypeDouble #undef FcStrCopy #undef FcStrCopyFilename #undef FcStrPlus @@ -397,6 +465,8 @@ FcBool (*FcDirCacheUnlink_dylibloader_wrapper_fontconfig)(const FcChar8*, FcConf FcBool (*FcDirCacheValid_dylibloader_wrapper_fontconfig)(const FcChar8*); FcBool (*FcDirCacheClean_dylibloader_wrapper_fontconfig)(const FcChar8*, FcBool); void (*FcCacheCreateTagFile_dylibloader_wrapper_fontconfig)(const FcConfig*); +FcBool (*FcDirCacheCreateUUID_dylibloader_wrapper_fontconfig)( FcChar8*, FcBool, FcConfig*); +FcBool (*FcDirCacheDeleteUUID_dylibloader_wrapper_fontconfig)(const FcChar8*, FcConfig*); FcChar8* (*FcConfigHome_dylibloader_wrapper_fontconfig)( void); FcBool (*FcConfigEnableHome_dylibloader_wrapper_fontconfig)( FcBool); FcChar8* (*FcConfigFilename_dylibloader_wrapper_fontconfig)(const FcChar8*); @@ -423,6 +493,26 @@ FcBool (*FcConfigSubstituteWithPat_dylibloader_wrapper_fontconfig)( FcConfig*, F FcBool (*FcConfigSubstitute_dylibloader_wrapper_fontconfig)( FcConfig*, FcPattern*, FcMatchKind); const FcChar8* (*FcConfigGetSysRoot_dylibloader_wrapper_fontconfig)(const FcConfig*); void (*FcConfigSetSysRoot_dylibloader_wrapper_fontconfig)( FcConfig*,const FcChar8*); +void (*FcConfigFileInfoIterInit_dylibloader_wrapper_fontconfig)( FcConfig*, FcConfigFileInfoIter*); +FcBool (*FcConfigFileInfoIterNext_dylibloader_wrapper_fontconfig)( FcConfig*, FcConfigFileInfoIter*); +FcBool (*FcConfigFileInfoIterGet_dylibloader_wrapper_fontconfig)( FcConfig*, FcConfigFileInfoIter*, FcChar8**, FcChar8**, FcBool*); +FcCharSet* (*FcCharSetCreate_dylibloader_wrapper_fontconfig)( void); +FcCharSet* (*FcCharSetNew_dylibloader_wrapper_fontconfig)( void); +void (*FcCharSetDestroy_dylibloader_wrapper_fontconfig)( FcCharSet*); +FcBool (*FcCharSetAddChar_dylibloader_wrapper_fontconfig)( FcCharSet*, FcChar32); +FcBool (*FcCharSetDelChar_dylibloader_wrapper_fontconfig)( FcCharSet*, FcChar32); +FcCharSet* (*FcCharSetCopy_dylibloader_wrapper_fontconfig)( FcCharSet*); +FcBool (*FcCharSetEqual_dylibloader_wrapper_fontconfig)(const FcCharSet*,const FcCharSet*); +FcCharSet* (*FcCharSetIntersect_dylibloader_wrapper_fontconfig)(const FcCharSet*,const FcCharSet*); +FcCharSet* (*FcCharSetUnion_dylibloader_wrapper_fontconfig)(const FcCharSet*,const FcCharSet*); +FcCharSet* (*FcCharSetSubtract_dylibloader_wrapper_fontconfig)(const FcCharSet*,const FcCharSet*); +FcBool (*FcCharSetMerge_dylibloader_wrapper_fontconfig)( FcCharSet*,const FcCharSet*, FcBool*); +FcBool (*FcCharSetHasChar_dylibloader_wrapper_fontconfig)(const FcCharSet*, FcChar32); +FcChar32 (*FcCharSetCount_dylibloader_wrapper_fontconfig)(const FcCharSet*); +FcChar32 (*FcCharSetIntersectCount_dylibloader_wrapper_fontconfig)(const FcCharSet*,const FcCharSet*); +FcChar32 (*FcCharSetSubtractCount_dylibloader_wrapper_fontconfig)(const FcCharSet*,const FcCharSet*); +FcBool (*FcCharSetIsSubset_dylibloader_wrapper_fontconfig)(const FcCharSet*,const FcCharSet*); +FcChar32 (*FcCharSetCoverage_dylibloader_wrapper_fontconfig)(const FcCharSet*, FcChar32, FcChar32*); void (*FcValuePrint_dylibloader_wrapper_fontconfig)(const FcValue); void (*FcPatternPrint_dylibloader_wrapper_fontconfig)(const FcPattern*); void (*FcFontSetPrint_dylibloader_wrapper_fontconfig)(const FcFontSet*); @@ -437,7 +527,8 @@ FcCache* (*FcDirCacheRescan_dylibloader_wrapper_fontconfig)(const FcChar8*, FcCo FcCache* (*FcDirCacheRead_dylibloader_wrapper_fontconfig)(const FcChar8*, FcBool, FcConfig*); FcCache* (*FcDirCacheLoadFile_dylibloader_wrapper_fontconfig)(const FcChar8*,struct stat*); void (*FcDirCacheUnload_dylibloader_wrapper_fontconfig)( FcCache*); -FcPattern* (*FcFreeTypeQuery_dylibloader_wrapper_fontconfig)(const FcChar8*, int, FcBlanks*, int*); +FcPattern* (*FcFreeTypeQuery_dylibloader_wrapper_fontconfig)(const FcChar8*, unsigned int, FcBlanks*, int*); +unsigned int (*FcFreeTypeQueryAll_dylibloader_wrapper_fontconfig)(const FcChar8*, unsigned int, FcBlanks*, int*, FcFontSet*); FcFontSet* (*FcFontSetCreate_dylibloader_wrapper_fontconfig)( void); void (*FcFontSetDestroy_dylibloader_wrapper_fontconfig)( FcFontSet*); FcBool (*FcFontSetAdd_dylibloader_wrapper_fontconfig)( FcFontSet*, FcPattern*); @@ -508,6 +599,7 @@ void (*FcValueDestroy_dylibloader_wrapper_fontconfig)( FcValue); FcBool (*FcValueEqual_dylibloader_wrapper_fontconfig)( FcValue, FcValue); FcValue (*FcValueSave_dylibloader_wrapper_fontconfig)( FcValue); void (*FcPatternDestroy_dylibloader_wrapper_fontconfig)( FcPattern*); +int (*FcPatternObjectCount_dylibloader_wrapper_fontconfig)(const FcPattern*); FcBool (*FcPatternEqual_dylibloader_wrapper_fontconfig)(const FcPattern*,const FcPattern*); FcBool (*FcPatternEqualSubset_dylibloader_wrapper_fontconfig)(const FcPattern*,const FcPattern*,const FcObjectSet*); FcChar32 (*FcPatternHash_dylibloader_wrapper_fontconfig)(const FcPattern*); @@ -541,8 +633,18 @@ FcRange* (*FcRangeCreateInteger_dylibloader_wrapper_fontconfig)( FcChar32, FcCha void (*FcRangeDestroy_dylibloader_wrapper_fontconfig)( FcRange*); FcRange* (*FcRangeCopy_dylibloader_wrapper_fontconfig)(const FcRange*); FcBool (*FcRangeGetDouble_dylibloader_wrapper_fontconfig)(const FcRange*, double*, double*); +void (*FcPatternIterStart_dylibloader_wrapper_fontconfig)(const FcPattern*, FcPatternIter*); +FcBool (*FcPatternIterNext_dylibloader_wrapper_fontconfig)(const FcPattern*, FcPatternIter*); +FcBool (*FcPatternIterEqual_dylibloader_wrapper_fontconfig)(const FcPattern*, FcPatternIter*,const FcPattern*, FcPatternIter*); +FcBool (*FcPatternFindIter_dylibloader_wrapper_fontconfig)(const FcPattern*, FcPatternIter*,const char*); +FcBool (*FcPatternIterIsValid_dylibloader_wrapper_fontconfig)(const FcPattern*, FcPatternIter*); +const char* (*FcPatternIterGetObject_dylibloader_wrapper_fontconfig)(const FcPattern*, FcPatternIter*); +int (*FcPatternIterValueCount_dylibloader_wrapper_fontconfig)(const FcPattern*, FcPatternIter*); +FcResult (*FcPatternIterGetValue_dylibloader_wrapper_fontconfig)(const FcPattern*, FcPatternIter*, int, FcValue*, FcValueBinding*); int (*FcWeightFromOpenType_dylibloader_wrapper_fontconfig)( int); +double (*FcWeightFromOpenTypeDouble_dylibloader_wrapper_fontconfig)( double); int (*FcWeightToOpenType_dylibloader_wrapper_fontconfig)( int); +double (*FcWeightToOpenTypeDouble_dylibloader_wrapper_fontconfig)( double); FcChar8* (*FcStrCopy_dylibloader_wrapper_fontconfig)(const FcChar8*); FcChar8* (*FcStrCopyFilename_dylibloader_wrapper_fontconfig)(const FcChar8*); FcChar8* (*FcStrPlus_dylibloader_wrapper_fontconfig)(const FcChar8*,const FcChar8*); @@ -687,6 +789,22 @@ int initialize_fontconfig(int verbose) { fprintf(stderr, "%s\n", error); } } +// FcDirCacheCreateUUID + *(void **) (&FcDirCacheCreateUUID_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcDirCacheCreateUUID"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcDirCacheDeleteUUID + *(void **) (&FcDirCacheDeleteUUID_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcDirCacheDeleteUUID"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } // FcConfigHome *(void **) (&FcConfigHome_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcConfigHome"); if (verbose) { @@ -895,6 +1013,166 @@ int initialize_fontconfig(int verbose) { fprintf(stderr, "%s\n", error); } } +// FcConfigFileInfoIterInit + *(void **) (&FcConfigFileInfoIterInit_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcConfigFileInfoIterInit"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcConfigFileInfoIterNext + *(void **) (&FcConfigFileInfoIterNext_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcConfigFileInfoIterNext"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcConfigFileInfoIterGet + *(void **) (&FcConfigFileInfoIterGet_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcConfigFileInfoIterGet"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetCreate + *(void **) (&FcCharSetCreate_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetCreate"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetNew + *(void **) (&FcCharSetNew_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetNew"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetDestroy + *(void **) (&FcCharSetDestroy_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetDestroy"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetAddChar + *(void **) (&FcCharSetAddChar_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetAddChar"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetDelChar + *(void **) (&FcCharSetDelChar_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetDelChar"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetCopy + *(void **) (&FcCharSetCopy_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetCopy"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetEqual + *(void **) (&FcCharSetEqual_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetEqual"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetIntersect + *(void **) (&FcCharSetIntersect_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetIntersect"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetUnion + *(void **) (&FcCharSetUnion_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetUnion"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetSubtract + *(void **) (&FcCharSetSubtract_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetSubtract"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetMerge + *(void **) (&FcCharSetMerge_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetMerge"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetHasChar + *(void **) (&FcCharSetHasChar_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetHasChar"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetCount + *(void **) (&FcCharSetCount_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetCount"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetIntersectCount + *(void **) (&FcCharSetIntersectCount_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetIntersectCount"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetSubtractCount + *(void **) (&FcCharSetSubtractCount_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetSubtractCount"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetIsSubset + *(void **) (&FcCharSetIsSubset_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetIsSubset"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcCharSetCoverage + *(void **) (&FcCharSetCoverage_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcCharSetCoverage"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } // FcValuePrint *(void **) (&FcValuePrint_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcValuePrint"); if (verbose) { @@ -1015,6 +1293,14 @@ int initialize_fontconfig(int verbose) { fprintf(stderr, "%s\n", error); } } +// FcFreeTypeQueryAll + *(void **) (&FcFreeTypeQueryAll_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcFreeTypeQueryAll"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } // FcFontSetCreate *(void **) (&FcFontSetCreate_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcFontSetCreate"); if (verbose) { @@ -1575,6 +1861,14 @@ int initialize_fontconfig(int verbose) { fprintf(stderr, "%s\n", error); } } +// FcPatternObjectCount + *(void **) (&FcPatternObjectCount_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcPatternObjectCount"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } // FcPatternEqual *(void **) (&FcPatternEqual_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcPatternEqual"); if (verbose) { @@ -1839,6 +2133,70 @@ int initialize_fontconfig(int verbose) { fprintf(stderr, "%s\n", error); } } +// FcPatternIterStart + *(void **) (&FcPatternIterStart_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcPatternIterStart"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcPatternIterNext + *(void **) (&FcPatternIterNext_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcPatternIterNext"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcPatternIterEqual + *(void **) (&FcPatternIterEqual_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcPatternIterEqual"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcPatternFindIter + *(void **) (&FcPatternFindIter_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcPatternFindIter"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcPatternIterIsValid + *(void **) (&FcPatternIterIsValid_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcPatternIterIsValid"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcPatternIterGetObject + *(void **) (&FcPatternIterGetObject_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcPatternIterGetObject"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcPatternIterValueCount + *(void **) (&FcPatternIterValueCount_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcPatternIterValueCount"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } +// FcPatternIterGetValue + *(void **) (&FcPatternIterGetValue_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcPatternIterGetValue"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } // FcWeightFromOpenType *(void **) (&FcWeightFromOpenType_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcWeightFromOpenType"); if (verbose) { @@ -1847,6 +2205,14 @@ int initialize_fontconfig(int verbose) { fprintf(stderr, "%s\n", error); } } +// FcWeightFromOpenTypeDouble + *(void **) (&FcWeightFromOpenTypeDouble_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcWeightFromOpenTypeDouble"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } // FcWeightToOpenType *(void **) (&FcWeightToOpenType_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcWeightToOpenType"); if (verbose) { @@ -1855,6 +2221,14 @@ int initialize_fontconfig(int verbose) { fprintf(stderr, "%s\n", error); } } +// FcWeightToOpenTypeDouble + *(void **) (&FcWeightToOpenTypeDouble_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcWeightToOpenTypeDouble"); + if (verbose) { + error = dlerror(); + if (error != NULL) { + fprintf(stderr, "%s\n", error); + } + } // FcStrCopy *(void **) (&FcStrCopy_dylibloader_wrapper_fontconfig) = dlsym(handle, "FcStrCopy"); if (verbose) { diff --git a/platform/linuxbsd/fontconfig-so_wrap.h b/platform/linuxbsd/fontconfig-so_wrap.h index f0794cce8b..0c8259deb7 100644 --- a/platform/linuxbsd/fontconfig-so_wrap.h +++ b/platform/linuxbsd/fontconfig-so_wrap.h @@ -2,8 +2,8 @@ #define DYLIBLOAD_WRAPPER_FONTCONFIG // This file is generated. Do not edit! // see https://github.com/hpvb/dynload-wrapper for details -// generated by ./generate-wrapper.py 0.3 on 2022-07-29 05:40:07 -// flags: ./generate-wrapper.py --include /usr/include/fontconfig/fontconfig.h --sys-include <fontconfig/fontconfig.h> --soname libfontconfig.so --init-name fontconfig --output-header fontconfig-so_wrap.h --output-implementation fontconfig-so_wrap.c --omit-prefix FcCharSet +// generated by ./generate-wrapper.py 0.3 on 2022-11-22 10:28:00 +// flags: ./generate-wrapper.py --include /usr/include/fontconfig/fontconfig.h --sys-include <fontconfig/fontconfig.h> --soname libfontconfig.so --init-name fontconfig --output-header fontconfig-so_wrap.h --output-implementation fontconfig-so_wrap.c --omit-prefix FcCharSetFirst --omit-prefix FcCharSetNext // #include <stdint.h> @@ -20,6 +20,8 @@ #define FcDirCacheValid FcDirCacheValid_dylibloader_orig_fontconfig #define FcDirCacheClean FcDirCacheClean_dylibloader_orig_fontconfig #define FcCacheCreateTagFile FcCacheCreateTagFile_dylibloader_orig_fontconfig +#define FcDirCacheCreateUUID FcDirCacheCreateUUID_dylibloader_orig_fontconfig +#define FcDirCacheDeleteUUID FcDirCacheDeleteUUID_dylibloader_orig_fontconfig #define FcConfigHome FcConfigHome_dylibloader_orig_fontconfig #define FcConfigEnableHome FcConfigEnableHome_dylibloader_orig_fontconfig #define FcConfigFilename FcConfigFilename_dylibloader_orig_fontconfig @@ -46,6 +48,26 @@ #define FcConfigSubstitute FcConfigSubstitute_dylibloader_orig_fontconfig #define FcConfigGetSysRoot FcConfigGetSysRoot_dylibloader_orig_fontconfig #define FcConfigSetSysRoot FcConfigSetSysRoot_dylibloader_orig_fontconfig +#define FcConfigFileInfoIterInit FcConfigFileInfoIterInit_dylibloader_orig_fontconfig +#define FcConfigFileInfoIterNext FcConfigFileInfoIterNext_dylibloader_orig_fontconfig +#define FcConfigFileInfoIterGet FcConfigFileInfoIterGet_dylibloader_orig_fontconfig +#define FcCharSetCreate FcCharSetCreate_dylibloader_orig_fontconfig +#define FcCharSetNew FcCharSetNew_dylibloader_orig_fontconfig +#define FcCharSetDestroy FcCharSetDestroy_dylibloader_orig_fontconfig +#define FcCharSetAddChar FcCharSetAddChar_dylibloader_orig_fontconfig +#define FcCharSetDelChar FcCharSetDelChar_dylibloader_orig_fontconfig +#define FcCharSetCopy FcCharSetCopy_dylibloader_orig_fontconfig +#define FcCharSetEqual FcCharSetEqual_dylibloader_orig_fontconfig +#define FcCharSetIntersect FcCharSetIntersect_dylibloader_orig_fontconfig +#define FcCharSetUnion FcCharSetUnion_dylibloader_orig_fontconfig +#define FcCharSetSubtract FcCharSetSubtract_dylibloader_orig_fontconfig +#define FcCharSetMerge FcCharSetMerge_dylibloader_orig_fontconfig +#define FcCharSetHasChar FcCharSetHasChar_dylibloader_orig_fontconfig +#define FcCharSetCount FcCharSetCount_dylibloader_orig_fontconfig +#define FcCharSetIntersectCount FcCharSetIntersectCount_dylibloader_orig_fontconfig +#define FcCharSetSubtractCount FcCharSetSubtractCount_dylibloader_orig_fontconfig +#define FcCharSetIsSubset FcCharSetIsSubset_dylibloader_orig_fontconfig +#define FcCharSetCoverage FcCharSetCoverage_dylibloader_orig_fontconfig #define FcValuePrint FcValuePrint_dylibloader_orig_fontconfig #define FcPatternPrint FcPatternPrint_dylibloader_orig_fontconfig #define FcFontSetPrint FcFontSetPrint_dylibloader_orig_fontconfig @@ -61,6 +83,7 @@ #define FcDirCacheLoadFile FcDirCacheLoadFile_dylibloader_orig_fontconfig #define FcDirCacheUnload FcDirCacheUnload_dylibloader_orig_fontconfig #define FcFreeTypeQuery FcFreeTypeQuery_dylibloader_orig_fontconfig +#define FcFreeTypeQueryAll FcFreeTypeQueryAll_dylibloader_orig_fontconfig #define FcFontSetCreate FcFontSetCreate_dylibloader_orig_fontconfig #define FcFontSetDestroy FcFontSetDestroy_dylibloader_orig_fontconfig #define FcFontSetAdd FcFontSetAdd_dylibloader_orig_fontconfig @@ -131,6 +154,7 @@ #define FcValueEqual FcValueEqual_dylibloader_orig_fontconfig #define FcValueSave FcValueSave_dylibloader_orig_fontconfig #define FcPatternDestroy FcPatternDestroy_dylibloader_orig_fontconfig +#define FcPatternObjectCount FcPatternObjectCount_dylibloader_orig_fontconfig #define FcPatternEqual FcPatternEqual_dylibloader_orig_fontconfig #define FcPatternEqualSubset FcPatternEqualSubset_dylibloader_orig_fontconfig #define FcPatternHash FcPatternHash_dylibloader_orig_fontconfig @@ -164,8 +188,18 @@ #define FcRangeDestroy FcRangeDestroy_dylibloader_orig_fontconfig #define FcRangeCopy FcRangeCopy_dylibloader_orig_fontconfig #define FcRangeGetDouble FcRangeGetDouble_dylibloader_orig_fontconfig +#define FcPatternIterStart FcPatternIterStart_dylibloader_orig_fontconfig +#define FcPatternIterNext FcPatternIterNext_dylibloader_orig_fontconfig +#define FcPatternIterEqual FcPatternIterEqual_dylibloader_orig_fontconfig +#define FcPatternFindIter FcPatternFindIter_dylibloader_orig_fontconfig +#define FcPatternIterIsValid FcPatternIterIsValid_dylibloader_orig_fontconfig +#define FcPatternIterGetObject FcPatternIterGetObject_dylibloader_orig_fontconfig +#define FcPatternIterValueCount FcPatternIterValueCount_dylibloader_orig_fontconfig +#define FcPatternIterGetValue FcPatternIterGetValue_dylibloader_orig_fontconfig #define FcWeightFromOpenType FcWeightFromOpenType_dylibloader_orig_fontconfig +#define FcWeightFromOpenTypeDouble FcWeightFromOpenTypeDouble_dylibloader_orig_fontconfig #define FcWeightToOpenType FcWeightToOpenType_dylibloader_orig_fontconfig +#define FcWeightToOpenTypeDouble FcWeightToOpenTypeDouble_dylibloader_orig_fontconfig #define FcStrCopy FcStrCopy_dylibloader_orig_fontconfig #define FcStrCopyFilename FcStrCopyFilename_dylibloader_orig_fontconfig #define FcStrPlus FcStrPlus_dylibloader_orig_fontconfig @@ -209,6 +243,8 @@ #undef FcDirCacheValid #undef FcDirCacheClean #undef FcCacheCreateTagFile +#undef FcDirCacheCreateUUID +#undef FcDirCacheDeleteUUID #undef FcConfigHome #undef FcConfigEnableHome #undef FcConfigFilename @@ -235,6 +271,26 @@ #undef FcConfigSubstitute #undef FcConfigGetSysRoot #undef FcConfigSetSysRoot +#undef FcConfigFileInfoIterInit +#undef FcConfigFileInfoIterNext +#undef FcConfigFileInfoIterGet +#undef FcCharSetCreate +#undef FcCharSetNew +#undef FcCharSetDestroy +#undef FcCharSetAddChar +#undef FcCharSetDelChar +#undef FcCharSetCopy +#undef FcCharSetEqual +#undef FcCharSetIntersect +#undef FcCharSetUnion +#undef FcCharSetSubtract +#undef FcCharSetMerge +#undef FcCharSetHasChar +#undef FcCharSetCount +#undef FcCharSetIntersectCount +#undef FcCharSetSubtractCount +#undef FcCharSetIsSubset +#undef FcCharSetCoverage #undef FcValuePrint #undef FcPatternPrint #undef FcFontSetPrint @@ -250,6 +306,7 @@ #undef FcDirCacheLoadFile #undef FcDirCacheUnload #undef FcFreeTypeQuery +#undef FcFreeTypeQueryAll #undef FcFontSetCreate #undef FcFontSetDestroy #undef FcFontSetAdd @@ -320,6 +377,7 @@ #undef FcValueEqual #undef FcValueSave #undef FcPatternDestroy +#undef FcPatternObjectCount #undef FcPatternEqual #undef FcPatternEqualSubset #undef FcPatternHash @@ -353,8 +411,18 @@ #undef FcRangeDestroy #undef FcRangeCopy #undef FcRangeGetDouble +#undef FcPatternIterStart +#undef FcPatternIterNext +#undef FcPatternIterEqual +#undef FcPatternFindIter +#undef FcPatternIterIsValid +#undef FcPatternIterGetObject +#undef FcPatternIterValueCount +#undef FcPatternIterGetValue #undef FcWeightFromOpenType +#undef FcWeightFromOpenTypeDouble #undef FcWeightToOpenType +#undef FcWeightToOpenTypeDouble #undef FcStrCopy #undef FcStrCopyFilename #undef FcStrPlus @@ -400,6 +468,8 @@ extern "C" { #define FcDirCacheValid FcDirCacheValid_dylibloader_wrapper_fontconfig #define FcDirCacheClean FcDirCacheClean_dylibloader_wrapper_fontconfig #define FcCacheCreateTagFile FcCacheCreateTagFile_dylibloader_wrapper_fontconfig +#define FcDirCacheCreateUUID FcDirCacheCreateUUID_dylibloader_wrapper_fontconfig +#define FcDirCacheDeleteUUID FcDirCacheDeleteUUID_dylibloader_wrapper_fontconfig #define FcConfigHome FcConfigHome_dylibloader_wrapper_fontconfig #define FcConfigEnableHome FcConfigEnableHome_dylibloader_wrapper_fontconfig #define FcConfigFilename FcConfigFilename_dylibloader_wrapper_fontconfig @@ -426,6 +496,26 @@ extern "C" { #define FcConfigSubstitute FcConfigSubstitute_dylibloader_wrapper_fontconfig #define FcConfigGetSysRoot FcConfigGetSysRoot_dylibloader_wrapper_fontconfig #define FcConfigSetSysRoot FcConfigSetSysRoot_dylibloader_wrapper_fontconfig +#define FcConfigFileInfoIterInit FcConfigFileInfoIterInit_dylibloader_wrapper_fontconfig +#define FcConfigFileInfoIterNext FcConfigFileInfoIterNext_dylibloader_wrapper_fontconfig +#define FcConfigFileInfoIterGet FcConfigFileInfoIterGet_dylibloader_wrapper_fontconfig +#define FcCharSetCreate FcCharSetCreate_dylibloader_wrapper_fontconfig +#define FcCharSetNew FcCharSetNew_dylibloader_wrapper_fontconfig +#define FcCharSetDestroy FcCharSetDestroy_dylibloader_wrapper_fontconfig +#define FcCharSetAddChar FcCharSetAddChar_dylibloader_wrapper_fontconfig +#define FcCharSetDelChar FcCharSetDelChar_dylibloader_wrapper_fontconfig +#define FcCharSetCopy FcCharSetCopy_dylibloader_wrapper_fontconfig +#define FcCharSetEqual FcCharSetEqual_dylibloader_wrapper_fontconfig +#define FcCharSetIntersect FcCharSetIntersect_dylibloader_wrapper_fontconfig +#define FcCharSetUnion FcCharSetUnion_dylibloader_wrapper_fontconfig +#define FcCharSetSubtract FcCharSetSubtract_dylibloader_wrapper_fontconfig +#define FcCharSetMerge FcCharSetMerge_dylibloader_wrapper_fontconfig +#define FcCharSetHasChar FcCharSetHasChar_dylibloader_wrapper_fontconfig +#define FcCharSetCount FcCharSetCount_dylibloader_wrapper_fontconfig +#define FcCharSetIntersectCount FcCharSetIntersectCount_dylibloader_wrapper_fontconfig +#define FcCharSetSubtractCount FcCharSetSubtractCount_dylibloader_wrapper_fontconfig +#define FcCharSetIsSubset FcCharSetIsSubset_dylibloader_wrapper_fontconfig +#define FcCharSetCoverage FcCharSetCoverage_dylibloader_wrapper_fontconfig #define FcValuePrint FcValuePrint_dylibloader_wrapper_fontconfig #define FcPatternPrint FcPatternPrint_dylibloader_wrapper_fontconfig #define FcFontSetPrint FcFontSetPrint_dylibloader_wrapper_fontconfig @@ -441,6 +531,7 @@ extern "C" { #define FcDirCacheLoadFile FcDirCacheLoadFile_dylibloader_wrapper_fontconfig #define FcDirCacheUnload FcDirCacheUnload_dylibloader_wrapper_fontconfig #define FcFreeTypeQuery FcFreeTypeQuery_dylibloader_wrapper_fontconfig +#define FcFreeTypeQueryAll FcFreeTypeQueryAll_dylibloader_wrapper_fontconfig #define FcFontSetCreate FcFontSetCreate_dylibloader_wrapper_fontconfig #define FcFontSetDestroy FcFontSetDestroy_dylibloader_wrapper_fontconfig #define FcFontSetAdd FcFontSetAdd_dylibloader_wrapper_fontconfig @@ -511,6 +602,7 @@ extern "C" { #define FcValueEqual FcValueEqual_dylibloader_wrapper_fontconfig #define FcValueSave FcValueSave_dylibloader_wrapper_fontconfig #define FcPatternDestroy FcPatternDestroy_dylibloader_wrapper_fontconfig +#define FcPatternObjectCount FcPatternObjectCount_dylibloader_wrapper_fontconfig #define FcPatternEqual FcPatternEqual_dylibloader_wrapper_fontconfig #define FcPatternEqualSubset FcPatternEqualSubset_dylibloader_wrapper_fontconfig #define FcPatternHash FcPatternHash_dylibloader_wrapper_fontconfig @@ -544,8 +636,18 @@ extern "C" { #define FcRangeDestroy FcRangeDestroy_dylibloader_wrapper_fontconfig #define FcRangeCopy FcRangeCopy_dylibloader_wrapper_fontconfig #define FcRangeGetDouble FcRangeGetDouble_dylibloader_wrapper_fontconfig +#define FcPatternIterStart FcPatternIterStart_dylibloader_wrapper_fontconfig +#define FcPatternIterNext FcPatternIterNext_dylibloader_wrapper_fontconfig +#define FcPatternIterEqual FcPatternIterEqual_dylibloader_wrapper_fontconfig +#define FcPatternFindIter FcPatternFindIter_dylibloader_wrapper_fontconfig +#define FcPatternIterIsValid FcPatternIterIsValid_dylibloader_wrapper_fontconfig +#define FcPatternIterGetObject FcPatternIterGetObject_dylibloader_wrapper_fontconfig +#define FcPatternIterValueCount FcPatternIterValueCount_dylibloader_wrapper_fontconfig +#define FcPatternIterGetValue FcPatternIterGetValue_dylibloader_wrapper_fontconfig #define FcWeightFromOpenType FcWeightFromOpenType_dylibloader_wrapper_fontconfig +#define FcWeightFromOpenTypeDouble FcWeightFromOpenTypeDouble_dylibloader_wrapper_fontconfig #define FcWeightToOpenType FcWeightToOpenType_dylibloader_wrapper_fontconfig +#define FcWeightToOpenTypeDouble FcWeightToOpenTypeDouble_dylibloader_wrapper_fontconfig #define FcStrCopy FcStrCopy_dylibloader_wrapper_fontconfig #define FcStrCopyFilename FcStrCopyFilename_dylibloader_wrapper_fontconfig #define FcStrPlus FcStrPlus_dylibloader_wrapper_fontconfig @@ -588,6 +690,8 @@ extern FcBool (*FcDirCacheUnlink_dylibloader_wrapper_fontconfig)(const FcChar8*, extern FcBool (*FcDirCacheValid_dylibloader_wrapper_fontconfig)(const FcChar8*); extern FcBool (*FcDirCacheClean_dylibloader_wrapper_fontconfig)(const FcChar8*, FcBool); extern void (*FcCacheCreateTagFile_dylibloader_wrapper_fontconfig)(const FcConfig*); +extern FcBool (*FcDirCacheCreateUUID_dylibloader_wrapper_fontconfig)( FcChar8*, FcBool, FcConfig*); +extern FcBool (*FcDirCacheDeleteUUID_dylibloader_wrapper_fontconfig)(const FcChar8*, FcConfig*); extern FcChar8* (*FcConfigHome_dylibloader_wrapper_fontconfig)( void); extern FcBool (*FcConfigEnableHome_dylibloader_wrapper_fontconfig)( FcBool); extern FcChar8* (*FcConfigFilename_dylibloader_wrapper_fontconfig)(const FcChar8*); @@ -614,6 +718,26 @@ extern FcBool (*FcConfigSubstituteWithPat_dylibloader_wrapper_fontconfig)( FcCon extern FcBool (*FcConfigSubstitute_dylibloader_wrapper_fontconfig)( FcConfig*, FcPattern*, FcMatchKind); extern const FcChar8* (*FcConfigGetSysRoot_dylibloader_wrapper_fontconfig)(const FcConfig*); extern void (*FcConfigSetSysRoot_dylibloader_wrapper_fontconfig)( FcConfig*,const FcChar8*); +extern void (*FcConfigFileInfoIterInit_dylibloader_wrapper_fontconfig)( FcConfig*, FcConfigFileInfoIter*); +extern FcBool (*FcConfigFileInfoIterNext_dylibloader_wrapper_fontconfig)( FcConfig*, FcConfigFileInfoIter*); +extern FcBool (*FcConfigFileInfoIterGet_dylibloader_wrapper_fontconfig)( FcConfig*, FcConfigFileInfoIter*, FcChar8**, FcChar8**, FcBool*); +extern FcCharSet* (*FcCharSetCreate_dylibloader_wrapper_fontconfig)( void); +extern FcCharSet* (*FcCharSetNew_dylibloader_wrapper_fontconfig)( void); +extern void (*FcCharSetDestroy_dylibloader_wrapper_fontconfig)( FcCharSet*); +extern FcBool (*FcCharSetAddChar_dylibloader_wrapper_fontconfig)( FcCharSet*, FcChar32); +extern FcBool (*FcCharSetDelChar_dylibloader_wrapper_fontconfig)( FcCharSet*, FcChar32); +extern FcCharSet* (*FcCharSetCopy_dylibloader_wrapper_fontconfig)( FcCharSet*); +extern FcBool (*FcCharSetEqual_dylibloader_wrapper_fontconfig)(const FcCharSet*,const FcCharSet*); +extern FcCharSet* (*FcCharSetIntersect_dylibloader_wrapper_fontconfig)(const FcCharSet*,const FcCharSet*); +extern FcCharSet* (*FcCharSetUnion_dylibloader_wrapper_fontconfig)(const FcCharSet*,const FcCharSet*); +extern FcCharSet* (*FcCharSetSubtract_dylibloader_wrapper_fontconfig)(const FcCharSet*,const FcCharSet*); +extern FcBool (*FcCharSetMerge_dylibloader_wrapper_fontconfig)( FcCharSet*,const FcCharSet*, FcBool*); +extern FcBool (*FcCharSetHasChar_dylibloader_wrapper_fontconfig)(const FcCharSet*, FcChar32); +extern FcChar32 (*FcCharSetCount_dylibloader_wrapper_fontconfig)(const FcCharSet*); +extern FcChar32 (*FcCharSetIntersectCount_dylibloader_wrapper_fontconfig)(const FcCharSet*,const FcCharSet*); +extern FcChar32 (*FcCharSetSubtractCount_dylibloader_wrapper_fontconfig)(const FcCharSet*,const FcCharSet*); +extern FcBool (*FcCharSetIsSubset_dylibloader_wrapper_fontconfig)(const FcCharSet*,const FcCharSet*); +extern FcChar32 (*FcCharSetCoverage_dylibloader_wrapper_fontconfig)(const FcCharSet*, FcChar32, FcChar32*); extern void (*FcValuePrint_dylibloader_wrapper_fontconfig)(const FcValue); extern void (*FcPatternPrint_dylibloader_wrapper_fontconfig)(const FcPattern*); extern void (*FcFontSetPrint_dylibloader_wrapper_fontconfig)(const FcFontSet*); @@ -628,7 +752,8 @@ extern FcCache* (*FcDirCacheRescan_dylibloader_wrapper_fontconfig)(const FcChar8 extern FcCache* (*FcDirCacheRead_dylibloader_wrapper_fontconfig)(const FcChar8*, FcBool, FcConfig*); extern FcCache* (*FcDirCacheLoadFile_dylibloader_wrapper_fontconfig)(const FcChar8*,struct stat*); extern void (*FcDirCacheUnload_dylibloader_wrapper_fontconfig)( FcCache*); -extern FcPattern* (*FcFreeTypeQuery_dylibloader_wrapper_fontconfig)(const FcChar8*, int, FcBlanks*, int*); +extern FcPattern* (*FcFreeTypeQuery_dylibloader_wrapper_fontconfig)(const FcChar8*, unsigned int, FcBlanks*, int*); +extern unsigned int (*FcFreeTypeQueryAll_dylibloader_wrapper_fontconfig)(const FcChar8*, unsigned int, FcBlanks*, int*, FcFontSet*); extern FcFontSet* (*FcFontSetCreate_dylibloader_wrapper_fontconfig)( void); extern void (*FcFontSetDestroy_dylibloader_wrapper_fontconfig)( FcFontSet*); extern FcBool (*FcFontSetAdd_dylibloader_wrapper_fontconfig)( FcFontSet*, FcPattern*); @@ -699,6 +824,7 @@ extern void (*FcValueDestroy_dylibloader_wrapper_fontconfig)( FcValue); extern FcBool (*FcValueEqual_dylibloader_wrapper_fontconfig)( FcValue, FcValue); extern FcValue (*FcValueSave_dylibloader_wrapper_fontconfig)( FcValue); extern void (*FcPatternDestroy_dylibloader_wrapper_fontconfig)( FcPattern*); +extern int (*FcPatternObjectCount_dylibloader_wrapper_fontconfig)(const FcPattern*); extern FcBool (*FcPatternEqual_dylibloader_wrapper_fontconfig)(const FcPattern*,const FcPattern*); extern FcBool (*FcPatternEqualSubset_dylibloader_wrapper_fontconfig)(const FcPattern*,const FcPattern*,const FcObjectSet*); extern FcChar32 (*FcPatternHash_dylibloader_wrapper_fontconfig)(const FcPattern*); @@ -732,8 +858,18 @@ extern FcRange* (*FcRangeCreateInteger_dylibloader_wrapper_fontconfig)( FcChar32 extern void (*FcRangeDestroy_dylibloader_wrapper_fontconfig)( FcRange*); extern FcRange* (*FcRangeCopy_dylibloader_wrapper_fontconfig)(const FcRange*); extern FcBool (*FcRangeGetDouble_dylibloader_wrapper_fontconfig)(const FcRange*, double*, double*); +extern void (*FcPatternIterStart_dylibloader_wrapper_fontconfig)(const FcPattern*, FcPatternIter*); +extern FcBool (*FcPatternIterNext_dylibloader_wrapper_fontconfig)(const FcPattern*, FcPatternIter*); +extern FcBool (*FcPatternIterEqual_dylibloader_wrapper_fontconfig)(const FcPattern*, FcPatternIter*,const FcPattern*, FcPatternIter*); +extern FcBool (*FcPatternFindIter_dylibloader_wrapper_fontconfig)(const FcPattern*, FcPatternIter*,const char*); +extern FcBool (*FcPatternIterIsValid_dylibloader_wrapper_fontconfig)(const FcPattern*, FcPatternIter*); +extern const char* (*FcPatternIterGetObject_dylibloader_wrapper_fontconfig)(const FcPattern*, FcPatternIter*); +extern int (*FcPatternIterValueCount_dylibloader_wrapper_fontconfig)(const FcPattern*, FcPatternIter*); +extern FcResult (*FcPatternIterGetValue_dylibloader_wrapper_fontconfig)(const FcPattern*, FcPatternIter*, int, FcValue*, FcValueBinding*); extern int (*FcWeightFromOpenType_dylibloader_wrapper_fontconfig)( int); +extern double (*FcWeightFromOpenTypeDouble_dylibloader_wrapper_fontconfig)( double); extern int (*FcWeightToOpenType_dylibloader_wrapper_fontconfig)( int); +extern double (*FcWeightToOpenTypeDouble_dylibloader_wrapper_fontconfig)( double); extern FcChar8* (*FcStrCopy_dylibloader_wrapper_fontconfig)(const FcChar8*); extern FcChar8* (*FcStrCopyFilename_dylibloader_wrapper_fontconfig)(const FcChar8*); extern FcChar8* (*FcStrPlus_dylibloader_wrapper_fontconfig)(const FcChar8*,const FcChar8*); diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index bc018e366b..4eaf6965c9 100644 --- a/platform/linuxbsd/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -218,8 +218,8 @@ void JoypadLinux::monitor_joypads() { } } closedir(input_directory); + usleep(1000000); // 1s } - usleep(1000000); // 1s } void JoypadLinux::close_joypads() { diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index e14e4fb52d..d5a75edeea 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -56,10 +56,6 @@ #include <sys/utsname.h> #include <unistd.h> -#ifdef FONTCONFIG_ENABLED -#include "fontconfig-so_wrap.h" -#endif - void OS_LinuxBSD::alert(const String &p_alert, const String &p_title) { const char *message_programs[] = { "zenity", "kdialog", "Xdialog", "xmessage" }; @@ -585,15 +581,9 @@ Vector<String> OS_LinuxBSD::get_system_fonts() const { if (!font_config_initialized) { ERR_FAIL_V_MSG(Vector<String>(), "Unable to load fontconfig, system font support is disabled."); } + HashSet<String> font_names; Vector<String> ret; - - FcConfig *config = FcInitLoadConfigAndFonts(); - ERR_FAIL_COND_V(!config, ret); - - FcObjectSet *object_set = FcObjectSetBuild(FC_FAMILY, nullptr); - ERR_FAIL_COND_V(!object_set, ret); - static const char *allowed_formats[] = { "TrueType", "CFF" }; for (size_t i = 0; i < sizeof(allowed_formats) / sizeof(const char *); i++) { FcPattern *pattern = FcPatternCreate(); @@ -616,8 +606,6 @@ Vector<String> OS_LinuxBSD::get_system_fonts() const { } FcPatternDestroy(pattern); } - FcObjectSetDestroy(object_set); - FcConfigDestroy(config); for (const String &E : font_names) { ret.push_back(E); @@ -628,27 +616,122 @@ Vector<String> OS_LinuxBSD::get_system_fonts() const { #endif } -String OS_LinuxBSD::get_system_font_path(const String &p_font_name, bool p_bold, bool p_italic) const { +#ifdef FONTCONFIG_ENABLED +int OS_LinuxBSD::_weight_to_fc(int p_weight) const { + if (p_weight < 150) { + return FC_WEIGHT_THIN; + } else if (p_weight < 250) { + return FC_WEIGHT_EXTRALIGHT; + } else if (p_weight < 325) { + return FC_WEIGHT_LIGHT; + } else if (p_weight < 375) { + return FC_WEIGHT_DEMILIGHT; + } else if (p_weight < 390) { + return FC_WEIGHT_BOOK; + } else if (p_weight < 450) { + return FC_WEIGHT_REGULAR; + } else if (p_weight < 550) { + return FC_WEIGHT_MEDIUM; + } else if (p_weight < 650) { + return FC_WEIGHT_DEMIBOLD; + } else if (p_weight < 750) { + return FC_WEIGHT_BOLD; + } else if (p_weight < 850) { + return FC_WEIGHT_EXTRABOLD; + } else if (p_weight < 925) { + return FC_WEIGHT_BLACK; + } else { + return FC_WEIGHT_EXTRABLACK; + } +} + +int OS_LinuxBSD::_stretch_to_fc(int p_stretch) const { + if (p_stretch < 56) { + return FC_WIDTH_ULTRACONDENSED; + } else if (p_stretch < 69) { + return FC_WIDTH_EXTRACONDENSED; + } else if (p_stretch < 81) { + return FC_WIDTH_CONDENSED; + } else if (p_stretch < 93) { + return FC_WIDTH_SEMICONDENSED; + } else if (p_stretch < 106) { + return FC_WIDTH_NORMAL; + } else if (p_stretch < 137) { + return FC_WIDTH_SEMIEXPANDED; + } else if (p_stretch < 144) { + return FC_WIDTH_EXPANDED; + } else if (p_stretch < 162) { + return FC_WIDTH_EXTRAEXPANDED; + } else { + return FC_WIDTH_ULTRAEXPANDED; + } +} +#endif // FONTCONFIG_ENABLED + +Vector<String> OS_LinuxBSD::get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale, const String &p_script, int p_weight, int p_stretch, bool p_italic) const { #ifdef FONTCONFIG_ENABLED if (!font_config_initialized) { - ERR_FAIL_V_MSG(String(), "Unable to load fontconfig, system font support is disabled."); + ERR_FAIL_V_MSG(Vector<String>(), "Unable to load fontconfig, system font support is disabled."); } - bool allow_substitutes = (p_font_name.to_lower() == "sans-serif") || (p_font_name.to_lower() == "serif") || (p_font_name.to_lower() == "monospace") || (p_font_name.to_lower() == "cursive") || (p_font_name.to_lower() == "fantasy"); + Vector<String> ret; + FcPattern *pattern = FcPatternCreate(); + if (pattern) { + FcPatternAddString(pattern, FC_FAMILY, reinterpret_cast<const FcChar8 *>(p_font_name.utf8().get_data())); + FcPatternAddInteger(pattern, FC_WEIGHT, _weight_to_fc(p_weight)); + FcPatternAddInteger(pattern, FC_WIDTH, _stretch_to_fc(p_stretch)); + FcPatternAddInteger(pattern, FC_SLANT, p_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN); - String ret; + FcCharSet *char_set = FcCharSetCreate(); + for (int i = 0; i < p_text.size(); i++) { + FcCharSetAddChar(char_set, p_text[i]); + } + FcPatternAddCharSet(pattern, FC_CHARSET, char_set); - FcConfig *config = FcInitLoadConfigAndFonts(); - ERR_FAIL_COND_V(!config, ret); + FcLangSet *lang_set = FcLangSetCreate(); + FcLangSetAdd(lang_set, reinterpret_cast<const FcChar8 *>(p_locale.utf8().get_data())); + FcPatternAddLangSet(pattern, FC_LANG, lang_set); - FcObjectSet *object_set = FcObjectSetBuild(FC_FAMILY, FC_FILE, nullptr); - ERR_FAIL_COND_V(!object_set, ret); + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + FcResult result; + FcPattern *match = FcFontMatch(0, pattern, &result); + if (match) { + char *file_name = nullptr; + if (FcPatternGetString(match, FC_FILE, 0, reinterpret_cast<FcChar8 **>(&file_name)) == FcResultMatch) { + if (file_name) { + ret.push_back(String::utf8(file_name)); + } + } + FcPatternDestroy(match); + } + FcPatternDestroy(pattern); + FcCharSetDestroy(char_set); + FcLangSetDestroy(lang_set); + } + + return ret; +#else + ERR_FAIL_V_MSG(Vector<String>(), "Godot was compiled without fontconfig, system font support is disabled."); +#endif +} + +String OS_LinuxBSD::get_system_font_path(const String &p_font_name, int p_weight, int p_stretch, bool p_italic) const { +#ifdef FONTCONFIG_ENABLED + if (!font_config_initialized) { + ERR_FAIL_V_MSG(String(), "Unable to load fontconfig, system font support is disabled."); + } + + String ret; FcPattern *pattern = FcPatternCreate(); if (pattern) { + bool allow_substitutes = (p_font_name.to_lower() == "sans-serif") || (p_font_name.to_lower() == "serif") || (p_font_name.to_lower() == "monospace") || (p_font_name.to_lower() == "cursive") || (p_font_name.to_lower() == "fantasy"); + FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); FcPatternAddString(pattern, FC_FAMILY, reinterpret_cast<const FcChar8 *>(p_font_name.utf8().get_data())); - FcPatternAddInteger(pattern, FC_WEIGHT, p_bold ? FC_WEIGHT_BOLD : FC_WEIGHT_NORMAL); + FcPatternAddInteger(pattern, FC_WEIGHT, _weight_to_fc(p_weight)); + FcPatternAddInteger(pattern, FC_WIDTH, _stretch_to_fc(p_stretch)); FcPatternAddInteger(pattern, FC_SLANT, p_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN); FcConfigSubstitute(0, pattern, FcMatchPattern); @@ -663,8 +746,6 @@ String OS_LinuxBSD::get_system_font_path(const String &p_font_name, bool p_bold, if (family_name && String::utf8(family_name).to_lower() != p_font_name.to_lower()) { FcPatternDestroy(match); FcPatternDestroy(pattern); - FcObjectSetDestroy(object_set); - FcConfigDestroy(config); return String(); } @@ -681,8 +762,6 @@ String OS_LinuxBSD::get_system_font_path(const String &p_font_name, bool p_bold, } FcPatternDestroy(pattern); } - FcObjectSetDestroy(object_set); - FcConfigDestroy(config); return ret; #else @@ -1008,5 +1087,26 @@ OS_LinuxBSD::OS_LinuxBSD() { int dylibloader_verbose = 0; #endif font_config_initialized = (initialize_fontconfig(dylibloader_verbose) == 0); + if (font_config_initialized) { + config = FcInitLoadConfigAndFonts(); + if (!config) { + font_config_initialized = false; + } + object_set = FcObjectSetBuild(FC_FAMILY, FC_FILE, nullptr); + if (!object_set) { + font_config_initialized = false; + } + } +#endif // FONTCONFIG_ENABLED +} + +OS_LinuxBSD::~OS_LinuxBSD() { +#ifdef FONTCONFIG_ENABLED + if (object_set) { + FcObjectSetDestroy(object_set); + } + if (config) { + FcConfigDestroy(config); + } #endif // FONTCONFIG_ENABLED } diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index aa7af92aa1..bf469af568 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -40,11 +40,20 @@ #include "joypad_linux.h" #include "servers/audio_server.h" +#ifdef FONTCONFIG_ENABLED +#include "fontconfig-so_wrap.h" +#endif + class OS_LinuxBSD : public OS_Unix { virtual void delete_main_loop() override; #ifdef FONTCONFIG_ENABLED bool font_config_initialized = false; + FcConfig *config = nullptr; + FcObjectSet *object_set = nullptr; + + int _weight_to_fc(int p_weight) const; + int _stretch_to_fc(int p_stretch) const; #endif #ifdef JOYDEV_ENABLED @@ -94,7 +103,8 @@ public: virtual uint64_t get_embedded_pck_offset() const override; virtual Vector<String> get_system_fonts() const override; - virtual String get_system_font_path(const String &p_font_name, bool p_bold = false, bool p_italic = false) const override; + virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; + virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; virtual String get_config_path() const override; virtual String get_data_path() const override; @@ -119,6 +129,7 @@ public: virtual Error move_to_trash(const String &p_path) override; OS_LinuxBSD(); + ~OS_LinuxBSD(); }; #endif // OS_LINUXBSD_H diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index a5e3e60775..1c5c8b2d19 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -1397,8 +1397,8 @@ void DisplayServerX11::window_set_mouse_passthrough(const Vector<Vector2> &p_reg XRectangle rect; rect.x = 0; rect.y = 0; - rect.width = window_get_real_size(p_window).x; - rect.height = window_get_real_size(p_window).y; + rect.width = window_get_size_with_decorations(p_window).x; + rect.height = window_get_size_with_decorations(p_window).y; XUnionRectWithRegion(&rect, region, region); } else { XPoint *points = (XPoint *)memalloc(sizeof(XPoint) * p_region.size()); @@ -1582,7 +1582,7 @@ void DisplayServerX11::_update_size_hints(WindowID p_window) { xsh->width = wd.size.width; xsh->height = wd.size.height; - if (window_mode == WINDOW_MODE_FULLSCREEN) { + if (window_mode == WINDOW_MODE_FULLSCREEN || window_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { // Do not set any other hints to prevent the window manager from ignoring the fullscreen flags } else if (window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { // If resizing is disabled, use the forced size @@ -1618,6 +1618,40 @@ Point2i DisplayServerX11::window_get_position(WindowID p_window) const { return wd.position; } +Point2i DisplayServerX11::window_get_position_with_decorations(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); + const WindowData &wd = windows[p_window]; + + if (wd.fullscreen) { + return wd.position; + } + + XWindowAttributes xwa; + XSync(x11_display, False); + XGetWindowAttributes(x11_display, wd.x11_window, &xwa); + int x = wd.position.x; + int y = wd.position.y; + Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True); + if (prop != None) { + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = nullptr; + if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { + if (format == 32 && len == 4 && data) { + long *extents = (long *)data; + x -= extents[0]; // left + y -= extents[2]; // top + } + XFree(data); + } + } + return Size2i(x, y); +} + void DisplayServerX11::window_set_position(const Point2i &p_position, WindowID p_window) { _THREAD_SAFE_METHOD_ @@ -1762,12 +1796,16 @@ Size2i DisplayServerX11::window_get_size(WindowID p_window) const { return wd.size; } -Size2i DisplayServerX11::window_get_real_size(WindowID p_window) const { +Size2i DisplayServerX11::window_get_size_with_decorations(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); const WindowData &wd = windows[p_window]; + if (wd.fullscreen) { + return wd.size; + } + XWindowAttributes xwa; XSync(x11_display, False); XGetWindowAttributes(x11_display, wd.x11_window, &xwa); @@ -1949,7 +1987,7 @@ void DisplayServerX11::_validate_mode_on_map(WindowID p_window) { // Check if we applied any window modes that didn't take effect while unmapped const WindowData &wd = windows[p_window]; if (wd.fullscreen && !_window_fullscreen_check(p_window)) { - _set_wm_fullscreen(p_window, true); + _set_wm_fullscreen(p_window, true, wd.exclusive_fullscreen); } else if (wd.maximized && !_window_maximize_check(p_window, "_NET_WM_STATE")) { _set_wm_maximized(p_window, true); } else if (wd.minimized && !_window_minimize_check(p_window)) { @@ -2024,7 +2062,7 @@ void DisplayServerX11::_set_wm_minimized(WindowID p_window, bool p_enabled) { wd.minimized = p_enabled; } -void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { +void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled, bool p_exclusive) { ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; @@ -2063,7 +2101,14 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { // set bypass compositor hint Atom bypass_compositor = XInternAtom(x11_display, "_NET_WM_BYPASS_COMPOSITOR", False); - unsigned long compositing_disable_on = p_enabled ? 1 : 0; + unsigned long compositing_disable_on = 0; // Use default. + if (p_enabled) { + if (p_exclusive) { + compositing_disable_on = 1; // Force composition OFF to reduce overhead. + } else { + compositing_disable_on = 2; // Force composition ON to allow popup windows. + } + } if (bypass_compositor != None) { XChangeProperty(x11_display, wd.x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1); } @@ -2109,8 +2154,9 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { case WINDOW_MODE_FULLSCREEN: { //Remove full-screen wd.fullscreen = false; + wd.exclusive_fullscreen = false; - _set_wm_fullscreen(p_window, false); + _set_wm_fullscreen(p_window, false, false); //un-maximize required for always on top bool on_top = window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window); @@ -2143,7 +2189,13 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { } wd.fullscreen = true; - _set_wm_fullscreen(p_window, true); + if (p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { + wd.exclusive_fullscreen = true; + _set_wm_fullscreen(p_window, true, true); + } else { + wd.exclusive_fullscreen = false; + _set_wm_fullscreen(p_window, true, false); + } } break; case WINDOW_MODE_MAXIMIZED: { _set_wm_maximized(p_window, true); @@ -2158,7 +2210,11 @@ DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) c const WindowData &wd = windows[p_window]; if (wd.fullscreen) { //if fullscreen, it's not in another mode - return WINDOW_MODE_FULLSCREEN; + if (wd.exclusive_fullscreen) { + return WINDOW_MODE_EXCLUSIVE_FULLSCREEN; + } else { + return WINDOW_MODE_FULLSCREEN; + } } // Test maximized. @@ -3947,7 +4003,6 @@ void DisplayServerX11::process_events() { mb->set_window_id(window_id_other); mb->set_position(Vector2(x, y)); mb->set_global_position(mb->get_position()); - Input::get_singleton()->parse_input_event(mb); } break; } diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index c88a6b466a..2578ca06fd 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -159,6 +159,7 @@ class DisplayServerX11 : public DisplayServer { //better to guess on the fly, given WM can change it //WindowMode mode; bool fullscreen = false; //OS can't exit from this mode + bool exclusive_fullscreen = false; bool on_top = false; bool borderless = false; bool resize_disabled = false; @@ -283,7 +284,7 @@ class DisplayServerX11 : public DisplayServer { bool _window_minimize_check(WindowID p_window) const; void _validate_mode_on_map(WindowID p_window); void _update_size_hints(WindowID p_window); - void _set_wm_fullscreen(WindowID p_window, bool p_enabled); + void _set_wm_fullscreen(WindowID p_window, bool p_enabled, bool p_exclusive); void _set_wm_maximized(WindowID p_window, bool p_enabled); void _set_wm_minimized(WindowID p_window, bool p_enabled); @@ -392,6 +393,7 @@ public: virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; @@ -405,7 +407,7 @@ public: virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 8f315f736b..bd26f6e417 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -355,6 +355,7 @@ public: virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; @@ -368,7 +369,7 @@ public: virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index ad6143e16e..5c979dbf22 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -2414,6 +2414,27 @@ Point2i DisplayServerMacOS::window_get_position(WindowID p_window) const { return pos; } +Point2i DisplayServerMacOS::window_get_position_with_decorations(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Point2i()); + const WindowData &wd = windows[p_window]; + + const NSRect nsrect = [wd.window_object frame]; + Point2i pos; + + // Return the position of the top-left corner, for OS X the y starts at the bottom. + const float scale = screen_get_max_scale(); + pos.x = nsrect.origin.x; + pos.y = (nsrect.origin.y + nsrect.size.height); + pos *= scale; + pos -= _get_screens_origin(); + // OS X native y-coordinate relative to _get_screens_origin() is negative, + // Godot expects a positive value. + pos.y *= -1; + return pos; +} + void DisplayServerMacOS::window_set_position(const Point2i &p_position, WindowID p_window) { _THREAD_SAFE_METHOD_ @@ -2573,7 +2594,7 @@ Size2i DisplayServerMacOS::window_get_size(WindowID p_window) const { return wd.size; } -Size2i DisplayServerMacOS::window_get_real_size(WindowID p_window) const { +Size2i DisplayServerMacOS::window_get_size_with_decorations(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 5e71d10a3f..49c8c7758d 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -91,11 +91,14 @@ bool EditorExportPlatformMacOS::get_export_option_visibility(const EditorExportP return false; } } break; - case 2: { // "altool" + case 2: { // "notarytool" + // All options are visible. + } break; + case 3: { // "altool" // All options are visible. } break; default: { // disabled - if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password" || p_option == "notarization/apple_team_id" || p_option == "notarization/api_uuid" || p_option == "notarization/api_key") { + if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password" || p_option == "notarization/apple_team_id" || p_option == "notarization/api_uuid" || p_option == "notarization/api_key" || p_option == "notarization/api_key_id") { return false; } } break; @@ -129,9 +132,9 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), false)); #ifdef MACOS_ENABLED - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),PyOxidizer rcodesign,Xcode codesign"), 3, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),rcodesign,Xcode codesign"), 3, true)); #else - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),PyOxidizer rcodesign"), 1, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),rcodesign"), 1, true)); #endif // "codesign" only options: r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "Type: Name (ID)"), "")); @@ -165,17 +168,18 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray())); #ifdef MACOS_ENABLED - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,PyOxidizer rcodesign,Xcode altool"), 0, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,rcodesign,Xcode notarytool,Xcode altool (deprecated)"), 0, true)); #else - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,PyOxidizer rcodesign"), 0, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,rcodesign"), 0, true)); #endif - // "altool" only options: + // "altool" and "notarytool" only options: r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple ID email"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PASSWORD, "Enable two-factor authentication and provide app-specific password"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_team_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide team ID if your Apple ID belongs to multiple teams"), "")); - // "altool" and "rcodesign" only options: - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_uuid", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect issuer ID"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect API key ID"), "")); + // "altool", "notarytool" and "rcodesign" only options: + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_uuid", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect issuer ID UUID"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key", PROPERTY_HINT_GLOBAL_FILE, "*.p8"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect API key ID"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); @@ -498,7 +502,12 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres args.push_back(p_preset->get("notarization/api_uuid")); args.push_back("--api-key"); - args.push_back(p_preset->get("notarization/api_key")); + args.push_back(p_preset->get("notarization/api_key_id")); + + if (!p_preset->get("notarization/api_key").operator String().is_empty()) { + args.push_back("--api-key-path"); + args.push_back(p_preset->get("notarization/api_key")); + } args.push_back(p_path); @@ -519,7 +528,7 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres } else { print_verbose("rcodesign (" + p_path + "):\n" + str); int next_nl = str.find("\n", rq_offset); - String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14); + String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 23, -1) : str.substr(rq_offset + 23, next_nl - rq_offset - 23); add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid)); add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour.")); add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:")); @@ -529,7 +538,91 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres } } break; #ifdef MACOS_ENABLED - case 2: { // "altool" + case 2: { // "notarytool" + print_verbose("using notarytool notarization..."); + + if (!FileAccess::exists("/usr/bin/xcrun") && !FileAccess::exists("/bin/xcrun")) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Xcode command line tools are not installed.")); + return Error::FAILED; + } + + List<String> args; + + args.push_back("notarytool"); + args.push_back("submit"); + + args.push_back(p_path); + + if (p_preset->get("notarization/apple_id_name") == "" && p_preset->get("notarization/api_uuid") == "") { + add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Neither Apple ID name nor App Store Connect issuer ID name not specified.")); + return Error::FAILED; + } + if (p_preset->get("notarization/apple_id_name") != "" && p_preset->get("notarization/api_uuid") != "") { + add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Both Apple ID name and App Store Connect issuer ID name are specified, only one should be set at the same time.")); + return Error::FAILED; + } + + if (p_preset->get("notarization/apple_id_name") != "") { + if (p_preset->get("notarization/apple_id_password") == "") { + add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Apple ID password not specified.")); + return Error::FAILED; + } + args.push_back("--apple-id"); + args.push_back(p_preset->get("notarization/apple_id_name")); + + args.push_back("--password"); + args.push_back(p_preset->get("notarization/apple_id_password")); + } else { + if (p_preset->get("notarization/api_key_id") == "") { + add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("App Store Connect API key ID not specified.")); + return Error::FAILED; + } + args.push_back("--issuer"); + args.push_back(p_preset->get("notarization/api_uuid")); + + if (!p_preset->get("notarization/api_key").operator String().is_empty()) { + args.push_back("--key"); + args.push_back(p_preset->get("notarization/api_key")); + } + + args.push_back("--key-id"); + args.push_back(p_preset->get("notarization/api_key_id")); + } + + args.push_back("--no-progress"); + + if (p_preset->get("notarization/apple_team_id")) { + args.push_back("--team-id"); + args.push_back(p_preset->get("notarization/apple_team_id")); + } + + String str; + int exitcode = 0; + Error err = OS::get_singleton()->execute("xcrun", args, &str, &exitcode, true); + if (err != OK) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Could not start xcrun executable.")); + return err; + } + + int rq_offset = str.find("id:"); + if (exitcode != 0 || rq_offset == -1) { + print_line("notarytool (" + p_path + "):\n" + str); + add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed, see editor log for details.")); + return Error::FAILED; + } else { + print_verbose("notarytool (" + p_path + "):\n" + str); + int next_nl = str.find("\n", rq_offset); + String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 4, -1) : str.substr(rq_offset + 4, next_nl - rq_offset - 4); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid)); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour.")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun notarytool log <request uuid> --issuer <api uuid> --key-id <api key id> --key <api key path>\" or"); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun notarytool log <request uuid> --apple-id <your email> --password <app-specific pwd>>\""); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("Run the following command to staple the notarization ticket to the exported application (optional):")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun stapler staple <app path>\""); + } + } break; + case 3: { // "altool" print_verbose("using altool notarization..."); if (!FileAccess::exists("/usr/bin/xcrun") && !FileAccess::exists("/bin/xcrun")) { @@ -573,7 +666,7 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres args.push_back(p_preset->get("notarization/api_uuid")); args.push_back("--apiKey"); - args.push_back(p_preset->get("notarization/api_key")); + args.push_back(p_preset->get("notarization/api_key_id")); } args.push_back("--type"); @@ -595,7 +688,7 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres return err; } - int rq_offset = str.find("RequestUUID"); + int rq_offset = str.find("RequestUUID:"); if (exitcode != 0 || rq_offset == -1) { print_line("xcrun altool (" + p_path + "):\n" + str); add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed, see editor log for details.")); @@ -603,7 +696,7 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres } else { print_verbose("xcrun altool (" + p_path + "):\n" + str); int next_nl = str.find("\n", rq_offset); - String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14); + String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 13, -1) : str.substr(rq_offset + 13, next_nl - rq_offset - 13); add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid)); add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour. When the process is completed, you'll receive an email.")); add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:")); @@ -1819,7 +1912,7 @@ bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<Editor err += TTR("Notarization: Code signing is required for notarization.") + "\n"; valid = false; } - if (notary_tool == 2) { + if (notary_tool == 2 || notary_tool == 3) { if (!FileAccess::exists("/usr/bin/xcrun") && !FileAccess::exists("/bin/xcrun")) { err += TTR("Notarization: Xcode command line tools are not installed.") + "\n"; valid = false; @@ -1838,7 +1931,7 @@ bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<Editor } } if (p_preset->get("notarization/api_uuid") != "") { - if (p_preset->get("notarization/api_key") == "") { + if (p_preset->get("notarization/api_key_id") == "") { err += TTR("Notarization: App Store Connect API key ID not specified.") + "\n"; valid = false; } @@ -1849,7 +1942,7 @@ bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<Editor err += TTR("Notarization: App Store Connect issuer ID name not specified.") + "\n"; valid = false; } - if (p_preset->get("notarization/api_key") == "") { + if (p_preset->get("notarization/api_key_id") == "") { err += TTR("Notarization: App Store Connect API key ID not specified.") + "\n"; valid = false; } diff --git a/platform/macos/export/plist.cpp b/platform/macos/export/plist.cpp index cad014e65b..82ecd41d9c 100644 --- a/platform/macos/export/plist.cpp +++ b/platform/macos/export/plist.cpp @@ -353,7 +353,7 @@ bool PList::load_file(const String &p_filename) { } else { // Load text plist. Error err; - Vector<uint8_t> array = FileAccess::get_file_as_array(p_filename, &err); + Vector<uint8_t> array = FileAccess::get_file_as_bytes(p_filename, &err); ERR_FAIL_COND_V(err != OK, false); String ret; diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h index 46e7c17ebe..b2734e57cc 100644 --- a/platform/macos/os_macos.h +++ b/platform/macos/os_macos.h @@ -57,6 +57,10 @@ class OS_MacOS : public OS_Unix { List<String> launch_service_args; + CGFloat _weight_to_ct(int p_weight) const; + CGFloat _stretch_to_ct(int p_stretch) const; + String _get_default_fontname(const String &p_font_name) const; + static _FORCE_INLINE_ String get_framework_executable(const String &p_path); static void pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context); @@ -98,7 +102,8 @@ public: virtual String get_locale() const override; virtual Vector<String> get_system_fonts() const override; - virtual String get_system_font_path(const String &p_font_name, bool p_bold = false, bool p_italic = false) const override; + virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; + virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; virtual String get_executable_path() const override; virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override; diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index e620b058d3..ebba96ceb1 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -336,9 +336,7 @@ Vector<String> OS_MacOS::get_system_fonts() const { return ret; } -String OS_MacOS::get_system_font_path(const String &p_font_name, bool p_bold, bool p_italic) const { - String ret; - +String OS_MacOS::_get_default_fontname(const String &p_font_name) const { String font_name = p_font_name; if (font_name.to_lower() == "sans-serif") { font_name = "Helvetica"; @@ -351,21 +349,153 @@ String OS_MacOS::get_system_font_path(const String &p_font_name, bool p_bold, bo } else if (font_name.to_lower() == "cursive") { font_name = "Apple Chancery"; }; + return font_name; +} + +CGFloat OS_MacOS::_weight_to_ct(int p_weight) const { + if (p_weight < 150) { + return -0.80; + } else if (p_weight < 250) { + return -0.60; + } else if (p_weight < 350) { + return -0.40; + } else if (p_weight < 450) { + return 0.0; + } else if (p_weight < 550) { + return 0.23; + } else if (p_weight < 650) { + return 0.30; + } else if (p_weight < 750) { + return 0.40; + } else if (p_weight < 850) { + return 0.56; + } else if (p_weight < 925) { + return 0.62; + } else { + return 1.00; + } +} + +CGFloat OS_MacOS::_stretch_to_ct(int p_stretch) const { + if (p_stretch < 56) { + return -0.5; + } else if (p_stretch < 69) { + return -0.37; + } else if (p_stretch < 81) { + return -0.25; + } else if (p_stretch < 93) { + return -0.13; + } else if (p_stretch < 106) { + return 0.0; + } else if (p_stretch < 137) { + return 0.13; + } else if (p_stretch < 144) { + return 0.25; + } else if (p_stretch < 162) { + return 0.37; + } else { + return 0.5; + } +} + +Vector<String> OS_MacOS::get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale, const String &p_script, int p_weight, int p_stretch, bool p_italic) const { + Vector<String> ret; + String font_name = _get_default_fontname(p_font_name); + + CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8); + CTFontSymbolicTraits traits = 0; + if (p_weight >= 700) { + traits |= kCTFontBoldTrait; + } + if (p_italic) { + traits |= kCTFontItalicTrait; + } + if (p_stretch < 100) { + traits |= kCTFontCondensedTrait; + } else if (p_stretch > 100) { + traits |= kCTFontExpandedTrait; + } + + CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits); + CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); + CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits); + + CGFloat weight = _weight_to_ct(p_weight); + CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight); + CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight); + + CGFloat stretch = _stretch_to_ct(p_stretch); + CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch); + CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch); + + CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); + CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name); + CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict); + + CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes); + if (font) { + CTFontRef family = CTFontCreateWithFontDescriptor(font, 0, nullptr); + CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, p_text.utf8().get_data(), kCFStringEncodingUTF8); + CFRange range = CFRangeMake(0, CFStringGetLength(string)); + CTFontRef fallback_family = CTFontCreateForString(family, string, range); + if (fallback_family) { + CTFontDescriptorRef fallback_font = CTFontCopyFontDescriptor(fallback_family); + if (fallback_font) { + CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fallback_font, kCTFontURLAttribute); + if (url) { + NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]]; + ret.push_back(String::utf8([font_path UTF8String])); + CFRelease(url); + } + CFRelease(fallback_font); + } + CFRelease(fallback_family); + } + CFRelease(string); + CFRelease(font); + } + + CFRelease(attributes); + CFRelease(traits_dict); + CFRelease(sym_traits); + CFRelease(font_stretch); + CFRelease(font_weight); + CFRelease(name); + + return ret; +} + +String OS_MacOS::get_system_font_path(const String &p_font_name, int p_weight, int p_stretch, bool p_italic) const { + String ret; + String font_name = _get_default_fontname(p_font_name); CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8); CTFontSymbolicTraits traits = 0; - if (p_bold) { + if (p_weight > 700) { traits |= kCTFontBoldTrait; } if (p_italic) { traits |= kCTFontItalicTrait; } + if (p_stretch < 100) { + traits |= kCTFontCondensedTrait; + } else if (p_stretch > 100) { + traits |= kCTFontExpandedTrait; + } CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits); CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits); + CGFloat weight = _weight_to_ct(p_weight); + CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight); + CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight); + + CGFloat stretch = _stretch_to_ct(p_stretch); + CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch); + CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch); + CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr); CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name); CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict); @@ -384,6 +514,8 @@ String OS_MacOS::get_system_font_path(const String &p_font_name, bool p_bold, bo CFRelease(attributes); CFRelease(traits_dict); CFRelease(sym_traits); + CFRelease(font_stretch); + CFRelease(font_weight); CFRelease(name); return ret; diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp index f704124704..d057010c02 100644 --- a/platform/web/display_server_web.cpp +++ b/platform/web/display_server_web.cpp @@ -579,8 +579,8 @@ void DisplayServerWeb::touch_callback(int p_type, int p_count) { } } -bool DisplayServerWeb::screen_is_touchscreen(int p_screen) const { - return godot_js_display_touchscreen_is_available(); +bool DisplayServerWeb::is_touchscreen_available() const { + return godot_js_display_touchscreen_is_available() || (Input::get_singleton() && Input::get_singleton()->is_emulating_touch_from_mouse()); } // Virtual Keyboard @@ -943,7 +943,11 @@ void DisplayServerWeb::window_set_current_screen(int p_screen, WindowID p_window } Point2i DisplayServerWeb::window_get_position(WindowID p_window) const { - return Point2i(); // TODO Does this need implementation? + return Point2i(); +} + +Point2i DisplayServerWeb::window_get_position_with_decorations(WindowID p_window) const { + return Point2i(); } void DisplayServerWeb::window_set_position(const Point2i &p_position, WindowID p_window) { @@ -980,7 +984,7 @@ Size2i DisplayServerWeb::window_get_size(WindowID p_window) const { return Size2i(size[0], size[1]); } -Size2i DisplayServerWeb::window_get_real_size(WindowID p_window) const { +Size2i DisplayServerWeb::window_get_size_with_decorations(WindowID p_window) const { return window_get_size(p_window); } diff --git a/platform/web/display_server_web.h b/platform/web/display_server_web.h index 1919736802..fce98ec3d0 100644 --- a/platform/web/display_server_web.h +++ b/platform/web/display_server_web.h @@ -143,7 +143,7 @@ public: virtual Point2i mouse_get_position() const override; // touch - virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual bool is_touchscreen_available() const override; // clipboard virtual void clipboard_set(const String &p_text) override; @@ -182,6 +182,7 @@ public: virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; @@ -194,7 +195,7 @@ public: virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 705e83dace..1b55574b19 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -188,6 +188,7 @@ def get_opts(): BoolVariable("use_llvm", "Use the LLVM compiler", False), BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True), BoolVariable("use_asan", "Use address sanitizer (ASAN)", False), + BoolVariable("debug_crt", "Compile with MSVC's debug CRT (/MDd)", False), ] @@ -339,10 +340,14 @@ def configure_msvc(env, vcvars_msvc_config): ## Compile/link flags - if env["use_static_cpp"]: - env.AppendUnique(CCFLAGS=["/MT"]) + if env["debug_crt"]: + # Always use dynamic runtime, static debug CRT breaks thread_local. + env.AppendUnique(CCFLAGS=["/MDd"]) else: - env.AppendUnique(CCFLAGS=["/MD"]) + if env["use_static_cpp"]: + env.AppendUnique(CCFLAGS=["/MT"]) + else: + env.AppendUnique(CCFLAGS=["/MD"]) if env["arch"] == "x86_32": env["x86_libtheora_opt_vc"] = True diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index c704a26b7a..e7864ebac0 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -909,6 +909,24 @@ Point2i DisplayServerWindows::window_get_position(WindowID p_window) const { return Point2i(point.x, point.y); } +Point2i DisplayServerWindows::window_get_position_with_decorations(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Point2i()); + const WindowData &wd = windows[p_window]; + + if (wd.minimized) { + return wd.last_pos; + } + + RECT r; + if (GetWindowRect(wd.hWnd, &r)) { + return Point2i(r.left, r.top); + } + + return Point2i(); +} + void DisplayServerWindows::_update_real_mouse_position(WindowID p_window) { ERR_FAIL_COND(!windows.has(p_window)); @@ -1124,7 +1142,7 @@ Size2i DisplayServerWindows::window_get_size(WindowID p_window) const { return Size2(); } -Size2i DisplayServerWindows::window_get_real_size(WindowID p_window) const { +Size2i DisplayServerWindows::window_get_size_with_decorations(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); @@ -2414,7 +2432,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_GETMINMAXINFO: { if (windows[window_id].resizable && !windows[window_id].fullscreen) { // Size of window decorations. - Size2 decor = window_get_real_size(window_id) - window_get_size(window_id); + Size2 decor = window_get_size_with_decorations(window_id) - window_get_size(window_id); MINMAXINFO *min_max_info = (MINMAXINFO *)lParam; if (windows[window_id].min_size != Size2()) { @@ -2473,7 +2491,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA window_mouseover_id = INVALID_WINDOW_ID; _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT); - } else if (window_mouseover_id != INVALID_WINDOW_ID) { + } else if (window_mouseover_id != INVALID_WINDOW_ID && windows.has(window_mouseover_id)) { // This is reached during drag and drop, after dropping in a different window. // Once-off notification, must call again. track_mouse_leave_event(windows[window_mouseover_id].hWnd); @@ -2712,7 +2730,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // Mouse enter. if (mouse_mode != MOUSE_MODE_CAPTURED) { - if (window_mouseover_id != INVALID_WINDOW_ID) { + if (window_mouseover_id != INVALID_WINDOW_ID && windows.has(window_mouseover_id)) { // Leave previous window. _send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT); } @@ -2814,7 +2832,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } DisplayServer::WindowID over_id = get_window_at_screen_position(mouse_get_position()); - if (!Rect2(window_get_position(over_id), Point2(windows[over_id].width, windows[over_id].height)).has_point(mouse_get_position())) { + if (windows.has(over_id) && !Rect2(window_get_position(over_id), Point2(windows[over_id].width, windows[over_id].height)).has_point(mouse_get_position())) { // Don't consider the windowborder as part of the window. over_id = INVALID_WINDOW_ID; } @@ -2822,12 +2840,12 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // Mouse enter. if (mouse_mode != MOUSE_MODE_CAPTURED) { - if (window_mouseover_id != INVALID_WINDOW_ID) { + if (window_mouseover_id != INVALID_WINDOW_ID && windows.has(window_mouseover_id)) { // Leave previous window. _send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT); } - if (over_id != INVALID_WINDOW_ID) { + if (over_id != INVALID_WINDOW_ID && windows.has(over_id)) { _send_window_event(windows[over_id], WINDOW_EVENT_MOUSE_ENTER); } } @@ -3520,7 +3538,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, DWORD dwExStyle; DWORD dwStyle; - _get_window_style(window_id_counter == MAIN_WINDOW_ID, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT), dwStyle, dwExStyle); + _get_window_style(window_id_counter == MAIN_WINDOW_ID, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), dwStyle, dwExStyle); RECT WindowRect; diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 8ac0086d69..4702bb7765 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -555,6 +555,7 @@ public: virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; @@ -568,7 +569,7 @@ public: virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; //wtf is this? should probable use proper name + virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index d8548eb545..e957a25e87 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -43,12 +43,12 @@ #include "platform/windows/display_server_windows.h" #include "servers/audio_server.h" #include "servers/rendering/rendering_server_default.h" +#include "servers/text_server.h" #include "windows_terminal_logger.h" #include <avrt.h> #include <bcrypt.h> #include <direct.h> -#include <dwrite.h> #include <knownfolders.h> #include <process.h> #include <regstr.h> @@ -189,6 +189,27 @@ void OS_Windows::initialize() { IPUnix::make_default(); main_loop = nullptr; + + CoInitialize(nullptr); + HRESULT hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown **>(&dwrite_factory)); + if (SUCCEEDED(hr)) { + hr = dwrite_factory->GetSystemFontCollection(&font_collection, false); + if (SUCCEEDED(hr)) { + dwrite_init = true; + hr = dwrite_factory->QueryInterface(&dwrite_factory2); + if (SUCCEEDED(hr)) { + hr = dwrite_factory2->GetSystemFontFallback(&system_font_fallback); + if (SUCCEEDED(hr)) { + dwrite2_init = true; + } + } + } + } + if (!dwrite_init) { + print_verbose("Unable to load IDWriteFactory, system font support is disabled."); + } else if (!dwrite2_init) { + print_verbose("Unable to load IDWriteFactory2, automatic system font fallback is disabled."); + } } void OS_Windows::delete_main_loop() { @@ -203,6 +224,22 @@ void OS_Windows::set_main_loop(MainLoop *p_main_loop) { } void OS_Windows::finalize() { + if (dwrite_factory2) { + dwrite_factory2->Release(); + dwrite_factory2 = nullptr; + } + if (font_collection) { + font_collection->Release(); + font_collection = nullptr; + } + if (system_font_fallback) { + system_font_fallback->Release(); + system_font_fallback = nullptr; + } + if (dwrite_factory) { + dwrite_factory->Release(); + dwrite_factory = nullptr; + } #ifdef WINMIDI_ENABLED driver_midi.close(); #endif @@ -726,21 +763,17 @@ Error OS_Windows::set_cwd(const String &p_cwd) { } Vector<String> OS_Windows::get_system_fonts() const { + if (!dwrite_init) { + return Vector<String>(); + } + Vector<String> ret; HashSet<String> font_names; - ComAutoreleaseRef<IDWriteFactory> dwrite_factory; - HRESULT hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown **>(&dwrite_factory.reference)); - ERR_FAIL_COND_V(FAILED(hr) || dwrite_factory.is_null(), ret); - - ComAutoreleaseRef<IDWriteFontCollection> font_collection; - hr = dwrite_factory->GetSystemFontCollection(&font_collection.reference, false); - ERR_FAIL_COND_V(FAILED(hr) || font_collection.is_null(), ret); - UINT32 family_count = font_collection->GetFontFamilyCount(); for (UINT32 i = 0; i < family_count; i++) { ComAutoreleaseRef<IDWriteFontFamily> family; - hr = font_collection->GetFontFamily(i, &family.reference); + HRESULT hr = font_collection->GetFontFamily(i, &family.reference); ERR_CONTINUE(FAILED(hr) || family.is_null()); ComAutoreleaseRef<IDWriteLocalizedStrings> family_names; @@ -771,7 +804,98 @@ Vector<String> OS_Windows::get_system_fonts() const { return ret; } -String OS_Windows::get_system_font_path(const String &p_font_name, bool p_bold, bool p_italic) const { +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif + +class FallbackTextAnalysisSource : public IDWriteTextAnalysisSource { + LONG _cRef = 1; + + bool rtl = false; + Char16String string; + Char16String locale; + IDWriteNumberSubstitution *n_sub = nullptr; + +public: + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface) { + if (IID_IUnknown == riid) { + AddRef(); + *ppvInterface = (IUnknown *)this; + } else if (__uuidof(IMMNotificationClient) == riid) { + AddRef(); + *ppvInterface = (IMMNotificationClient *)this; + } else { + *ppvInterface = nullptr; + return E_NOINTERFACE; + } + return S_OK; + } + + ULONG STDMETHODCALLTYPE AddRef() { + return InterlockedIncrement(&_cRef); + } + + ULONG STDMETHODCALLTYPE Release() { + ULONG ulRef = InterlockedDecrement(&_cRef); + if (0 == ulRef) { + delete this; + } + return ulRef; + } + + HRESULT STDMETHODCALLTYPE GetTextAtPosition(UINT32 p_text_position, WCHAR const **r_text_string, UINT32 *r_text_length) override { + if (p_text_position >= (UINT32)string.length()) { + *r_text_string = nullptr; + *r_text_length = 0; + return S_OK; + } + *r_text_string = reinterpret_cast<const wchar_t *>(string.get_data()) + p_text_position; + *r_text_length = string.length() - p_text_position; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE GetTextBeforePosition(UINT32 p_text_position, WCHAR const **r_text_string, UINT32 *r_text_length) override { + if (p_text_position < 1 || p_text_position >= (UINT32)string.length()) { + *r_text_string = nullptr; + *r_text_length = 0; + return S_OK; + } + *r_text_string = reinterpret_cast<const wchar_t *>(string.get_data()); + *r_text_length = p_text_position; + return S_OK; + } + + DWRITE_READING_DIRECTION STDMETHODCALLTYPE GetParagraphReadingDirection() override { + return (rtl) ? DWRITE_READING_DIRECTION_RIGHT_TO_LEFT : DWRITE_READING_DIRECTION_LEFT_TO_RIGHT; + } + + HRESULT STDMETHODCALLTYPE GetLocaleName(UINT32 p_text_position, UINT32 *r_text_length, WCHAR const **r_locale_name) override { + *r_locale_name = reinterpret_cast<const wchar_t *>(locale.get_data()); + return S_OK; + } + + HRESULT STDMETHODCALLTYPE GetNumberSubstitution(UINT32 p_text_position, UINT32 *r_text_length, IDWriteNumberSubstitution **r_number_substitution) override { + *r_number_substitution = n_sub; + return S_OK; + } + + FallbackTextAnalysisSource(const Char16String &p_text, const Char16String &p_locale, bool p_rtl, IDWriteNumberSubstitution *p_nsub) { + _cRef = 1; + string = p_text; + locale = p_locale; + n_sub = p_nsub; + rtl = p_rtl; + }; + + virtual ~FallbackTextAnalysisSource() {} +}; + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + +String OS_Windows::_get_default_fontname(const String &p_font_name) const { String font_name = p_font_name; if (font_name.to_lower() == "sans-serif") { font_name = "Arial"; @@ -784,18 +908,157 @@ String OS_Windows::get_system_font_path(const String &p_font_name, bool p_bold, } else if (font_name.to_lower() == "fantasy") { font_name = "Gabriola"; } + return font_name; +} + +DWRITE_FONT_WEIGHT OS_Windows::_weight_to_dw(int p_weight) const { + if (p_weight < 150) { + return DWRITE_FONT_WEIGHT_THIN; + } else if (p_weight < 250) { + return DWRITE_FONT_WEIGHT_EXTRA_LIGHT; + } else if (p_weight < 325) { + return DWRITE_FONT_WEIGHT_LIGHT; + } else if (p_weight < 375) { + return DWRITE_FONT_WEIGHT_SEMI_LIGHT; + } else if (p_weight < 450) { + return DWRITE_FONT_WEIGHT_NORMAL; + } else if (p_weight < 550) { + return DWRITE_FONT_WEIGHT_MEDIUM; + } else if (p_weight < 650) { + return DWRITE_FONT_WEIGHT_DEMI_BOLD; + } else if (p_weight < 750) { + return DWRITE_FONT_WEIGHT_BOLD; + } else if (p_weight < 850) { + return DWRITE_FONT_WEIGHT_EXTRA_BOLD; + } else if (p_weight < 925) { + return DWRITE_FONT_WEIGHT_BLACK; + } else { + return DWRITE_FONT_WEIGHT_EXTRA_BLACK; + } +} + +DWRITE_FONT_STRETCH OS_Windows::_stretch_to_dw(int p_stretch) const { + if (p_stretch < 56) { + return DWRITE_FONT_STRETCH_ULTRA_CONDENSED; + } else if (p_stretch < 69) { + return DWRITE_FONT_STRETCH_EXTRA_CONDENSED; + } else if (p_stretch < 81) { + return DWRITE_FONT_STRETCH_CONDENSED; + } else if (p_stretch < 93) { + return DWRITE_FONT_STRETCH_SEMI_CONDENSED; + } else if (p_stretch < 106) { + return DWRITE_FONT_STRETCH_NORMAL; + } else if (p_stretch < 137) { + return DWRITE_FONT_STRETCH_SEMI_EXPANDED; + } else if (p_stretch < 144) { + return DWRITE_FONT_STRETCH_EXPANDED; + } else if (p_stretch < 162) { + return DWRITE_FONT_STRETCH_EXTRA_EXPANDED; + } else { + return DWRITE_FONT_STRETCH_ULTRA_EXPANDED; + } +} + +Vector<String> OS_Windows::get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale, const String &p_script, int p_weight, int p_stretch, bool p_italic) const { + if (!dwrite2_init) { + return Vector<String>(); + } + + String font_name = _get_default_fontname(p_font_name); + + bool rtl = TS->is_locale_right_to_left(p_locale); + Char16String text = p_text.utf16(); + Char16String locale = p_locale.utf16(); + + ComAutoreleaseRef<IDWriteNumberSubstitution> number_substitution; + HRESULT hr = dwrite_factory->CreateNumberSubstitution(DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE, reinterpret_cast<const wchar_t *>(locale.get_data()), true, &number_substitution.reference); + ERR_FAIL_COND_V(FAILED(hr) || number_substitution.is_null(), Vector<String>()); + + FallbackTextAnalysisSource fs = FallbackTextAnalysisSource(text, locale, rtl, number_substitution.reference); + UINT32 mapped_length = 0; + FLOAT scale = 0.0; + ComAutoreleaseRef<IDWriteFont> dwrite_font; + hr = system_font_fallback->MapCharacters( + &fs, + 0, + (UINT32)text.length(), + font_collection, + reinterpret_cast<const wchar_t *>(font_name.utf16().get_data()), + _weight_to_dw(p_weight), + p_italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL, + _stretch_to_dw(p_stretch), + &mapped_length, + &dwrite_font.reference, + &scale); + + if (FAILED(hr) || dwrite_font.is_null()) { + return Vector<String>(); + } + + ComAutoreleaseRef<IDWriteFontFace> dwrite_face; + hr = dwrite_font->CreateFontFace(&dwrite_face.reference); + if (FAILED(hr) || dwrite_face.is_null()) { + return Vector<String>(); + } + + UINT32 number_of_files = 0; + hr = dwrite_face->GetFiles(&number_of_files, nullptr); + if (FAILED(hr)) { + return Vector<String>(); + } + Vector<ComAutoreleaseRef<IDWriteFontFile>> files; + files.resize(number_of_files); + hr = dwrite_face->GetFiles(&number_of_files, (IDWriteFontFile **)files.ptrw()); + if (FAILED(hr)) { + return Vector<String>(); + } + + Vector<String> ret; + for (UINT32 i = 0; i < number_of_files; i++) { + void const *reference_key = nullptr; + UINT32 reference_key_size = 0; + ComAutoreleaseRef<IDWriteLocalFontFileLoader> loader; + + hr = files.write[i]->GetLoader((IDWriteFontFileLoader **)&loader.reference); + if (FAILED(hr) || loader.is_null()) { + continue; + } + hr = files.write[i]->GetReferenceKey(&reference_key, &reference_key_size); + if (FAILED(hr)) { + continue; + } + + WCHAR file_path[MAX_PATH]; + hr = loader->GetFilePathFromKey(reference_key, reference_key_size, &file_path[0], MAX_PATH); + if (FAILED(hr)) { + continue; + } + String fpath = String::utf16((const char16_t *)&file_path[0]); + + WIN32_FIND_DATAW d; + HANDLE fnd = FindFirstFileW((LPCWSTR)&file_path[0], &d); + if (fnd != INVALID_HANDLE_VALUE) { + String fname = String::utf16((const char16_t *)d.cFileName); + if (!fname.is_empty()) { + fpath = fpath.get_base_dir().path_join(fname); + } + FindClose(fnd); + } + ret.push_back(fpath); + } + return ret; +} - ComAutoreleaseRef<IDWriteFactory> dwrite_factory; - HRESULT hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown **>(&dwrite_factory.reference)); - ERR_FAIL_COND_V(FAILED(hr) || dwrite_factory.is_null(), String()); +String OS_Windows::get_system_font_path(const String &p_font_name, int p_weight, int p_stretch, bool p_italic) const { + if (!dwrite_init) { + return String(); + } - ComAutoreleaseRef<IDWriteFontCollection> font_collection; - hr = dwrite_factory->GetSystemFontCollection(&font_collection.reference, false); - ERR_FAIL_COND_V(FAILED(hr) || font_collection.is_null(), String()); + String font_name = _get_default_fontname(p_font_name); UINT32 index = 0; BOOL exists = false; - font_collection->FindFamilyName((const WCHAR *)font_name.utf16().get_data(), &index, &exists); + HRESULT hr = font_collection->FindFamilyName((const WCHAR *)font_name.utf16().get_data(), &index, &exists); if (FAILED(hr)) { return String(); } @@ -807,7 +1070,7 @@ String OS_Windows::get_system_font_path(const String &p_font_name, bool p_bold, } ComAutoreleaseRef<IDWriteFont> dwrite_font; - hr = family->GetFirstMatchingFont(p_bold ? DWRITE_FONT_WEIGHT_BOLD : DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, p_italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL, &dwrite_font.reference); + hr = family->GetFirstMatchingFont(_weight_to_dw(p_weight), _stretch_to_dw(p_stretch), p_italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL, &dwrite_font.reference); if (FAILED(hr) || dwrite_font.is_null()) { return String(); } @@ -1192,7 +1455,7 @@ String OS_Windows::get_unique_id() const { bool OS_Windows::_check_internal_feature_support(const String &p_feature) { if (p_feature == "system_fonts") { - return true; + return dwrite_init; } if (p_feature == "pc") { return true; diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 6f89be699a..1db2b5880d 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -55,6 +55,8 @@ #include <stdio.h> #define WIN32_LEAN_AND_MEAN +#include <dwrite.h> +#include <dwrite_2.h> #include <windows.h> #include <windowsx.h> @@ -79,6 +81,9 @@ public: _FORCE_INLINE_ bool is_valid() const { return reference != nullptr; } _FORCE_INLINE_ bool is_null() const { return reference == nullptr; } ComAutoreleaseRef() {} + ComAutoreleaseRef(T *p_ref) { + reference = p_ref; + } ~ComAutoreleaseRef() { if (reference != nullptr) { reference->Release(); @@ -114,6 +119,18 @@ class OS_Windows : public OS { HWND main_window; + IDWriteFactory *dwrite_factory = nullptr; + IDWriteFactory2 *dwrite_factory2 = nullptr; + IDWriteFontCollection *font_collection = nullptr; + IDWriteFontFallback *system_font_fallback = nullptr; + + bool dwrite_init = false; + bool dwrite2_init = false; + + String _get_default_fontname(const String &p_font_name) const; + DWRITE_FONT_WEIGHT _weight_to_dw(int p_weight) const; + DWRITE_FONT_STRETCH _stretch_to_dw(int p_stretch) const; + // functions used by main to initialize/deinitialize the OS protected: virtual void initialize() override; @@ -172,7 +189,8 @@ public: virtual bool set_environment(const String &p_var, const String &p_value) const override; virtual Vector<String> get_system_fonts() const override; - virtual String get_system_font_path(const String &p_font_name, bool p_bold = false, bool p_italic = false) const override; + virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; + virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override; virtual String get_executable_path() const override; diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index b5945a4562..823b8d56e3 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -106,18 +106,57 @@ void Path2D::_notification(int p_what) { #else const real_t line_width = get_tree()->get_debug_paths_width(); #endif - _cached_draw_pts.resize(curve->get_point_count() * 8); - int count = 0; - - for (int i = 0; i < curve->get_point_count(); i++) { - for (int j = 0; j < 8; j++) { - real_t frac = j * (1.0 / 8.0); - Vector2 p = curve->sample(i, frac); - _cached_draw_pts.set(count++, p); + real_t interval = 10; + const real_t length = curve->get_baked_length(); + + if (length > CMP_EPSILON) { + const int sample_count = int(length / interval) + 2; + interval = length / (sample_count - 1); // Recalculate real interval length. + + Vector<Transform2D> frames; + frames.resize(sample_count); + + { + Transform2D *w = frames.ptrw(); + + for (int i = 0; i < sample_count; i++) { + w[i] = curve->sample_baked_with_rotation(i * interval, false); + } } - } - draw_polyline(_cached_draw_pts, get_tree()->get_debug_paths_color(), line_width, true); + const Transform2D *r = frames.ptr(); + // Draw curve segments + { + PackedVector2Array v2p; + v2p.resize(sample_count); + Vector2 *w = v2p.ptrw(); + + for (int i = 0; i < sample_count; i++) { + w[i] = r[i].get_origin(); + } + draw_polyline(v2p, get_tree()->get_debug_paths_color(), line_width, false); + } + + // Draw fish bones + { + PackedVector2Array v2p; + v2p.resize(3); + Vector2 *w = v2p.ptrw(); + + for (int i = 0; i < sample_count; i++) { + const Vector2 p = r[i].get_origin(); + const Vector2 side = r[i].columns[0]; + const Vector2 forward = r[i].columns[1]; + + // Fish Bone. + w[0] = p + (side - forward) * 5; + w[1] = p; + w[2] = p + (-side - forward) * 5; + + draw_polyline(v2p, get_tree()->get_debug_paths_color(), line_width * 0.5, false); + } + } + } } break; } } @@ -177,7 +216,7 @@ void PathFollow2D::_update_transform() { } if (rotates) { - Transform2D xform = c->sample_baked_with_rotation(progress, cubic, loop, lookahead); + Transform2D xform = c->sample_baked_with_rotation(progress, cubic); xform.translate_local(v_offset, h_offset); set_rotation(xform[1].angle()); set_position(xform[2]); diff --git a/scene/2d/path_2d.h b/scene/2d/path_2d.h index 5e436fb9f6..935717605a 100644 --- a/scene/2d/path_2d.h +++ b/scene/2d/path_2d.h @@ -38,7 +38,6 @@ class Path2D : public Node2D { GDCLASS(Path2D, Node2D); Ref<Curve2D> curve; - Vector<Vector2> _cached_draw_pts; void _curve_changed(); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 0159e9f313..4d4ea2b26d 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -3838,7 +3838,7 @@ void TileMap::set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) { } } -TypedArray<Vector2i> TileMap::get_surrounding_tiles(Vector2i coords) { +TypedArray<Vector2i> TileMap::get_surrounding_cells(Vector2i coords) { if (!tile_set.is_valid()) { return TypedArray<Vector2i>(); } @@ -4012,7 +4012,7 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("force_update", "layer"), &TileMap::force_update, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("get_surrounding_tiles", "coords"), &TileMap::get_surrounding_tiles); + ClassDB::bind_method(D_METHOD("get_surrounding_cells", "coords"), &TileMap::get_surrounding_cells); ClassDB::bind_method(D_METHOD("get_used_cells", "layer"), &TileMap::get_used_cells); ClassDB::bind_method(D_METHOD("get_used_rect"), &TileMap::get_used_rect); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 68a5d3c80b..19f0e5a553 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -400,7 +400,7 @@ public: void force_update(int p_layer = -1); // Helpers? - TypedArray<Vector2i> get_surrounding_tiles(Vector2i coords); + TypedArray<Vector2i> get_surrounding_cells(Vector2i coords); void draw_cells_outline(Control *p_control, RBSet<Vector2i> p_cells, Color p_color, Transform2D p_transform = Transform2D()); // Virtual function to modify the TileData at runtime diff --git a/scene/2d/touch_screen_button.cpp b/scene/2d/touch_screen_button.cpp index a02f322ef1..e99821e9b9 100644 --- a/scene/2d/touch_screen_button.cpp +++ b/scene/2d/touch_screen_button.cpp @@ -100,7 +100,7 @@ void TouchScreenButton::_notification(int p_what) { if (!is_inside_tree()) { return; } - if (!Engine::get_singleton()->is_editor_hint() && !!DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())) && visibility == VISIBILITY_TOUCHSCREEN_ONLY) { + if (!Engine::get_singleton()->is_editor_hint() && !!DisplayServer::get_singleton()->is_touchscreen_available() && visibility == VISIBILITY_TOUCHSCREEN_ONLY) { return; } @@ -137,7 +137,7 @@ void TouchScreenButton::_notification(int p_what) { } break; case NOTIFICATION_ENTER_TREE: { - if (!Engine::get_singleton()->is_editor_hint() && !!DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())) && visibility == VISIBILITY_TOUCHSCREEN_ONLY) { + if (!Engine::get_singleton()->is_editor_hint() && !!DisplayServer::get_singleton()->is_touchscreen_available() && visibility == VISIBILITY_TOUCHSCREEN_ONLY) { return; } queue_redraw(); diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp index 304e56326d..2a50575749 100644 --- a/scene/3d/camera_3d.cpp +++ b/scene/3d/camera_3d.cpp @@ -112,6 +112,12 @@ void Camera3D::_notification(int p_what) { if (current || first_camera) { viewport->_camera_3d_set(this); } + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + viewport->connect(SNAME("size_changed"), callable_mp((Node3D *)this, &Camera3D::update_gizmos)); + } +#endif } break; case NOTIFICATION_TRANSFORM_CHANGED: { @@ -133,6 +139,11 @@ void Camera3D::_notification(int p_what) { } if (viewport) { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + viewport->disconnect(SNAME("size_changed"), callable_mp((Node3D *)this, &Camera3D::update_gizmos)); + } +#endif viewport->_camera_3d_remove(this); viewport = nullptr; } diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp index 198dba7811..3f43e718b8 100644 --- a/scene/3d/light_3d.cpp +++ b/scene/3d/light_3d.cpp @@ -461,7 +461,7 @@ Light3D::Light3D(RenderingServer::LightType p_type) { set_param(PARAM_SHADOW_PANCAKE_SIZE, 20.0); set_param(PARAM_SHADOW_OPACITY, 1.0); set_param(PARAM_SHADOW_BLUR, 1.0); - set_param(PARAM_SHADOW_BIAS, 0.03); + set_param(PARAM_SHADOW_BIAS, 0.1); set_param(PARAM_SHADOW_NORMAL_BIAS, 1.0); set_param(PARAM_TRANSMITTANCE_BIAS, 0.05); set_param(PARAM_SHADOW_FADE_START, 1); @@ -571,8 +571,8 @@ DirectionalLight3D::DirectionalLight3D() : Light3D(RenderingServer::LIGHT_DIRECTIONAL) { set_param(PARAM_SHADOW_MAX_DISTANCE, 100); set_param(PARAM_SHADOW_FADE_START, 0.8); - // Increase the default shadow bias to better suit most scenes. - set_param(PARAM_SHADOW_BIAS, 0.1); + // Increase the default shadow normal bias to better suit most scenes. + set_param(PARAM_SHADOW_NORMAL_BIAS, 2.0); set_param(PARAM_INTENSITY, 100000.0); // Specified in Lux, approximate mid-day sun. set_shadow_mode(SHADOW_PARALLEL_4_SPLITS); blend_splits = false; @@ -614,8 +614,6 @@ void OmniLight3D::_bind_methods() { OmniLight3D::OmniLight3D() : Light3D(RenderingServer::LIGHT_OMNI) { set_shadow_mode(SHADOW_CUBE); - // Increase the default shadow biases to better suit most scenes. - set_param(PARAM_SHADOW_BIAS, 0.2); } PackedStringArray SpotLight3D::get_configuration_warnings() const { @@ -639,3 +637,9 @@ void SpotLight3D::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "spot_angle", PROPERTY_HINT_RANGE, "0,180,0.01,degrees"), "set_param", "get_param", PARAM_SPOT_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "spot_angle_attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_param", "get_param", PARAM_SPOT_ATTENUATION); } + +SpotLight3D::SpotLight3D() : + Light3D(RenderingServer::LIGHT_SPOT) { + // Decrease the default shadow bias to better suit most scenes. + set_param(PARAM_SHADOW_BIAS, 0.03); +} diff --git a/scene/3d/light_3d.h b/scene/3d/light_3d.h index 84d214030b..d2dfa32aac 100644 --- a/scene/3d/light_3d.h +++ b/scene/3d/light_3d.h @@ -233,8 +233,7 @@ protected: public: PackedStringArray get_configuration_warnings() const override; - SpotLight3D() : - Light3D(RenderingServer::LIGHT_SPOT) {} + SpotLight3D(); }; #endif // LIGHT_3D_H diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index 04d164ba88..88b1c340d8 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -322,6 +322,11 @@ void MeshInstance3D::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { _resolve_skeleton_path(); } break; + case NOTIFICATION_TRANSLATION_CHANGED: { + if (mesh.is_valid()) { + mesh->notification(NOTIFICATION_TRANSLATION_CHANGED); + } + } break; } } diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp index ca7d1dfc1d..05fc73306c 100644 --- a/scene/3d/xr_nodes.cpp +++ b/scene/3d/xr_nodes.cpp @@ -627,7 +627,9 @@ void XROrigin3D::set_world_scale(real_t p_world_scale) { xr_server->set_world_scale(p_world_scale); } -void XROrigin3D::set_current(bool p_enabled) { +void XROrigin3D::_set_current(bool p_enabled, bool p_update_others) { + // We run this logic even if current already equals p_enabled as we may have set this previously before we entered our tree. + // This is then called a second time on NOTIFICATION_ENTER_TREE where we actually process activating this origin node. current = p_enabled; if (!is_inside_tree() || Engine::get_singleton()->is_editor_hint()) { @@ -638,30 +640,38 @@ void XROrigin3D::set_current(bool p_enabled) { set_notify_local_transform(current); set_notify_transform(current); + // update XRServer with our current position if (current) { - for (int i = 0; i < origin_nodes.size(); i++) { - if (origin_nodes[i] != this) { - origin_nodes[i]->set_current(false); - } - } - - // update XRServer with our current position XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL(xr_server); xr_server->set_world_origin(get_global_transform()); - } else { - bool found = false; - // We no longer have a current origin so find the first one we can make current - for (int i = 0; !found && i < origin_nodes.size(); i++) { - if (origin_nodes[i] != this) { - origin_nodes[i]->set_current(true); - found = true; + } + + // Check if we need to update our other origin nodes accordingly + if (p_update_others) { + if (current) { + for (int i = 0; i < origin_nodes.size(); i++) { + if (origin_nodes[i] != this && origin_nodes[i]->current) { + origin_nodes[i]->_set_current(false, false); + } + } + } else { + // We no longer have a current origin so find the first one we can make current + for (int i = 0; i < origin_nodes.size(); i++) { + if (origin_nodes[i] != this) { + origin_nodes[i]->_set_current(true, false); + return; // we are done. + } } } } } +void XROrigin3D::set_current(bool p_enabled) { + _set_current(p_enabled, true); +} + bool XROrigin3D::is_current() const { if (Engine::get_singleton()->is_editor_hint()) { // return as is diff --git a/scene/3d/xr_nodes.h b/scene/3d/xr_nodes.h index 990fb61983..ec8e151a08 100644 --- a/scene/3d/xr_nodes.h +++ b/scene/3d/xr_nodes.h @@ -183,6 +183,8 @@ private: bool current = false; static Vector<XROrigin3D *> origin_nodes; // all origin nodes in tree + void _set_current(bool p_enabled, bool p_update_others); + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index ee6fb58583..ff3112ecbb 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -664,7 +664,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double } } - if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE || (p_delta == 0 && update_mode == Animation::UPDATE_DISCRETE)) { //delta == 0 means seek + if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE) { Variant value = a->value_track_interpolate(i, p_time); if (value == Variant()) { @@ -681,15 +681,23 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double pa->value_accum = Animation::interpolate_variant(pa->value_accum, value, p_interp); } - } else if (p_is_current && p_delta != 0) { + } else { List<int> indices; - if (p_started) { - int first_key = a->track_find_key(i, p_prev_time, true); - if (first_key >= 0) { - indices.push_back(first_key); + + if (p_seeked) { + int found_key = a->track_find_key(i, p_time); + if (found_key >= 0) { + indices.push_back(found_key); } + } else { + if (p_started) { + int first_key = a->track_find_key(i, p_prev_time, true); + if (first_key >= 0) { + indices.push_back(first_key); + } + } + a->track_get_key_indices_in_range(i, p_time, p_delta, &indices, p_looped_flag); } - a->track_get_key_indices_in_range(i, p_time, p_delta, &indices, p_looped_flag); for (int &F : indices) { Variant value = a->track_get_key_value(i, F); @@ -740,21 +748,26 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double if (!nc->node) { continue; } - if (p_delta == 0) { - continue; - } if (!p_is_current) { break; } List<int> indices; - if (p_started) { - int first_key = a->track_find_key(i, p_prev_time, true); - if (first_key >= 0) { - indices.push_back(first_key); + + if (p_seeked) { + int found_key = a->track_find_key(i, p_time); + if (found_key >= 0) { + indices.push_back(found_key); + } + } else { + if (p_started) { + int first_key = a->track_find_key(i, p_prev_time, true); + if (first_key >= 0) { + indices.push_back(first_key); + } } + a->track_get_key_indices_in_range(i, p_time, p_delta, &indices, p_looped_flag); } - a->track_get_key_indices_in_range(i, p_time, p_delta, &indices, p_looped_flag); for (int &E : indices) { StringName method = a->method_track_get_name(i, E); @@ -798,9 +811,6 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double if (!nc->node) { continue; } - if (p_delta == 0) { - continue; - } if (p_seeked) { //find whatever should be playing @@ -910,7 +920,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double continue; } - if (p_delta == 0 || p_seeked) { + if (p_seeked) { //seek int idx = a->track_find_key(i, p_time); if (idx < 0) { @@ -945,7 +955,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double break; } - if (player->is_playing() || p_seeked) { + if (player->is_playing()) { player->play(anim_name); player->seek(at_anim_pos); nc->animation_playing = true; @@ -991,6 +1001,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started) { double delta = p_delta * speed_scale * cd.speed_scale; double next_pos = cd.pos + delta; + bool backwards = signbit(delta); // Negative zero means playing backwards too. real_t len = cd.from->animation->get_length(); Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE; @@ -1002,23 +1013,7 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta, } else if (next_pos > len) { next_pos = len; } - - bool backwards = signbit(delta); // Negative zero means playing backwards too - delta = next_pos - cd.pos; // Fix delta (after determination of backwards because negative zero is lost here) - - if (&cd == &playback.current) { - if (!backwards && cd.pos <= len && next_pos == len) { - //playback finished - end_reached = true; - end_notify = cd.pos < len; // Notify only if not already at the end - } - - if (backwards && cd.pos >= 0 && next_pos == 0) { - //playback finished - end_reached = true; - end_notify = cd.pos > 0; // Notify only if not already at the beginning - } - } + delta = next_pos - cd.pos; // Fix delta (after determination of backwards because negative zero is lost here). } break; case Animation::LOOP_LINEAR: { @@ -1047,8 +1042,28 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta, break; } - _animation_process_animation(cd.from, cd.pos, next_pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started, looped_flag); + double prev_pos = cd.pos; // The animation may be changed during process, so it is safer that the state is changed before process. cd.pos = next_pos; + _animation_process_animation(cd.from, prev_pos, cd.pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started, looped_flag); + + if (is_just_played) { + return; // Animation has been changed in the process (may be caused by method track), abort process. + } + + if (cd.from->animation->get_loop_mode() == Animation::LOOP_NONE) { + if (&cd == &playback.current) { + if (!backwards && prev_pos <= len && next_pos == len) { + // Playback finished. + end_reached = true; + end_notify = prev_pos < len; // Notify only if not already at the end. + } + if (backwards && prev_pos >= 0 && next_pos == 0) { + // Playback finished. + end_reached = true; + end_notify = prev_pos > 0; // Notify only if not already at the beginning. + } + } + } } void AnimationPlayer::_animation_process2(double p_delta, bool p_started) { @@ -1056,23 +1071,25 @@ void AnimationPlayer::_animation_process2(double p_delta, bool p_started) { accum_pass++; - _animation_process_data(c.current, p_delta, 1.0f, c.seeked && p_delta != 0, p_started); + bool seeked = c.seeked; // The animation may be changed during process, so it is safer that the state is changed before process. if (p_delta != 0) { c.seeked = false; } + _animation_process_data(c.current, p_delta, 1.0f, seeked, p_started); + List<Blend>::Element *prev = nullptr; for (List<Blend>::Element *E = c.blend.back(); E; E = prev) { Blend &b = E->get(); float blend = b.blend_left / b.blend_time; - _animation_process_data(b.data, p_delta, blend, false, false); - b.blend_left -= Math::absf(speed_scale * p_delta); - prev = E->prev(); if (b.blend_left < 0) { c.blend.erase(E); } + // The effect of animation changes during blending is unknown... + // In that case, we recommends to use method call mode "deferred", not "immediate". + _animation_process_data(b.data, p_delta, blend, false, false); } } @@ -1192,13 +1209,24 @@ void AnimationPlayer::_animation_process(double p_delta) { if (playback.current.from) { end_reached = false; end_notify = false; - _animation_process2(p_delta, playback.started); + is_just_played = false; + bool started = playback.started; // The animation may be changed during process, so it is safer that the state is changed before process. if (playback.started) { playback.started = false; } + _animation_process2(p_delta, started); + + if (is_just_played) { + cache_update_size = 0; + cache_update_prop_size = 0; + cache_update_bezier_size = 0; + return; // Animation has been changed in the process (may be caused by method track), clear update caches and abort process. + } + _animation_update_transforms(); + if (end_reached) { if (queued.size()) { String old = playback.assigned; @@ -1647,6 +1675,7 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa c.assigned = name; c.seeked = false; c.started = true; + is_just_played = true; if (!end_reached) { queued.clear(); @@ -2135,7 +2164,7 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("advance", "delta"), &AnimationPlayer::advance); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_node"), "set_root", "get_root"); - ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "current_animation", PROPERTY_HINT_ENUM, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ANIMATE_AS_TRIGGER), "set_current_animation", "get_current_animation"); + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "current_animation", PROPERTY_HINT_ENUM, "", PROPERTY_USAGE_EDITOR), "set_current_animation", "get_current_animation"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "assigned_animation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_assigned_animation", "get_assigned_animation"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "autoplay", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_autoplay", "get_autoplay"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset_on_save", PROPERTY_HINT_NONE, ""), "set_reset_on_save_enabled", "is_reset_on_save_enabled"); diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index 0b95ee4e9e..352835a544 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -255,6 +255,7 @@ private: List<StringName> queued; + bool is_just_played = false; bool end_reached = false; bool end_notify = false; diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 16621faa33..c2d584b52f 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -586,7 +586,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { track_value->object = child; } - track_value->is_discrete = anim->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE || anim->value_track_get_update_mode(i) == Animation::UPDATE_TRIGGER; + track_value->is_discrete = anim->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE; track_value->is_using_angle = anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE; track_value->subpath = leftover_path; @@ -803,11 +803,11 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { TrackCacheValue *track_value = static_cast<TrackCacheValue *>(track); bool was_discrete = track_value->is_discrete; bool was_using_angle = track_value->is_using_angle; - track_value->is_discrete |= anim->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE || anim->value_track_get_update_mode(i) == Animation::UPDATE_TRIGGER; + track_value->is_discrete |= anim->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE; track_value->is_using_angle |= anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE; if (was_discrete != track_value->is_discrete) { - ERR_PRINT_ED("Value track: " + String(path) + " with different update modes are blended. Blending prioritizes Discrete/Trigger mode, so other update mode tracks will not be blended."); + ERR_PRINT_ED("Value track: " + String(path) + " with different update modes are blended. Blending prioritizes Discrete mode, so other update mode tracks will not be blended."); } if (was_using_angle != track_value->is_using_angle) { WARN_PRINT_ED("Value track: " + String(path) + " with different interpolation types for rotation are blended. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value."); @@ -1031,7 +1031,9 @@ void AnimationTree::_process_graph(double p_delta) { } NodePath path = a->track_get_path(i); - ERR_CONTINUE(!track_cache.has(path)); + if (!track_cache.has(path)) { + continue; // No path, but avoid error spamming. + } TrackCache *track = track_cache[path]; ERR_CONTINUE(!state.track_map.has(path)); @@ -1670,7 +1672,7 @@ void AnimationTree::_process_graph(double p_delta) { TrackCacheValue *t = static_cast<TrackCacheValue *>(track); if (t->is_discrete) { - break; // Don't overwrite the value set by UPDATE_DISCRETE or UPDATE_TRIGGER. + break; // Don't overwrite the value set by UPDATE_DISCRETE. } if (t->init_value.get_type() == Variant::BOOL) { diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index 599a759b08..28eedcbe13 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -220,9 +220,35 @@ void SceneDebugger::_save_node(ObjectID id, const String &p_path) { Node *node = Object::cast_to<Node>(ObjectDB::get_instance(id)); ERR_FAIL_COND(!node); +#ifdef TOOLS_ENABLED + HashMap<const Node *, Node *> duplimap; + Node *copy = node->duplicate_from_editor(duplimap); +#else + Node *copy = node->duplicate(); +#endif + + // Handle Unique Nodes. + for (int i = 0; i < copy->get_child_count(false); i++) { + _set_node_owner_recursive(copy->get_child(i, false), copy); + } + // Root node cannot ever be unique name in its own Scene! + copy->set_unique_name_in_owner(false); + Ref<PackedScene> ps = memnew(PackedScene); - ps->pack(node); + ps->pack(copy); ResourceSaver::save(ps, p_path); + + memdelete(copy); +} + +void SceneDebugger::_set_node_owner_recursive(Node *p_node, Node *p_owner) { + if (!p_node->get_owner()) { + p_node->set_owner(p_owner); + } + + for (int i = 0; i < p_node->get_child_count(false); i++) { + _set_node_owner_recursive(p_node->get_child(i, false), p_owner); + } } void SceneDebugger::_send_object_id(ObjectID p_id, int p_max_size) { diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h index 0428bfcc2e..34891ef0fd 100644 --- a/scene/debugger/scene_debugger.h +++ b/scene/debugger/scene_debugger.h @@ -56,6 +56,7 @@ public: #ifdef DEBUG_ENABLED private: static void _save_node(ObjectID id, const String &p_path); + static void _set_node_owner_recursive(Node *p_node, Node *p_owner); static void _set_object_property(ObjectID p_id, const String &p_property, const Variant &p_value); static void _send_object_id(ObjectID p_id, int p_max_size = 1 << 20); diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 5df4c066e4..83c789f3d5 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -366,38 +366,46 @@ void GraphNode::_notification(int p_what) { close_rect = Rect2(); } - for (const KeyValue<int, Slot> &E : slot_info) { - if (E.key < 0 || E.key >= cache_y.size()) { - continue; - } - if (!slot_info.has(E.key)) { - continue; - } - const Slot &s = slot_info[E.key]; - // Left port. - if (s.enable_left) { - Ref<Texture2D> p = port; - if (s.custom_slot_left.is_valid()) { - p = s.custom_slot_left; + if (get_child_count() > 0) { + for (const KeyValue<int, Slot> &E : slot_info) { + if (E.key < 0 || E.key >= cache_y.size()) { + continue; } - p->draw(get_canvas_item(), icofs + Point2(edgeofs, cache_y[E.key]), s.color_left); - } - // Right port. - if (s.enable_right) { - Ref<Texture2D> p = port; - if (s.custom_slot_right.is_valid()) { - p = s.custom_slot_right; + if (!slot_info.has(E.key)) { + continue; + } + const Slot &s = slot_info[E.key]; + // Left port. + if (s.enable_left) { + Ref<Texture2D> p = port; + if (s.custom_slot_left.is_valid()) { + p = s.custom_slot_left; + } + p->draw(get_canvas_item(), icofs + Point2(edgeofs, cache_y[E.key]), s.color_left); + } + // Right port. + if (s.enable_right) { + Ref<Texture2D> p = port; + if (s.custom_slot_right.is_valid()) { + p = s.custom_slot_right; + } + p->draw(get_canvas_item(), icofs + Point2(get_size().x - edgeofs, cache_y[E.key]), s.color_right); } - p->draw(get_canvas_item(), icofs + Point2(get_size().x - edgeofs, cache_y[E.key]), s.color_right); - } - // Draw slot stylebox. - if (s.draw_stylebox) { - Control *c = Object::cast_to<Control>(get_child(E.key)); - Rect2 c_rect = c->get_rect(); - c_rect.position.x = sb->get_margin(SIDE_LEFT); - c_rect.size.width = w; - draw_style_box(sb_slot, c_rect); + // Draw slot stylebox. + if (s.draw_stylebox) { + Control *c = Object::cast_to<Control>(get_child(E.key)); + if (!c || !c->is_visible_in_tree()) { + continue; + } + if (c->is_set_as_top_level()) { + continue; + } + Rect2 c_rect = c->get_rect(); + c_rect.position.x = sb->get_margin(SIDE_LEFT); + c_rect.size.width = w; + draw_style_box(sb_slot, c_rect); + } } } diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 8a77c39487..bd39ee3bb3 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -768,18 +768,18 @@ void LineEdit::_notification(int p_what) { case NOTIFICATION_WM_WINDOW_FOCUS_IN: { window_has_focus = true; - draw_caret = true; + _validate_caret_can_draw(); queue_redraw(); } break; case NOTIFICATION_WM_WINDOW_FOCUS_OUT: { window_has_focus = false; - draw_caret = false; + _validate_caret_can_draw(); queue_redraw(); } break; case NOTIFICATION_INTERNAL_PROCESS: { - if (caret_blinking) { + if (caret_blink_enabled && caret_can_draw) { caret_blink_timer += get_process_delta_time(); if (caret_blink_timer >= caret_blink_interval) { @@ -790,10 +790,6 @@ void LineEdit::_notification(int p_what) { } break; case NOTIFICATION_DRAW: { - if ((!has_focus() && !(menu && menu->has_focus()) && !caret_force_displayed) || !window_has_focus) { - draw_caret = false; - } - int width, height; bool rtl = is_layout_rtl(); @@ -806,7 +802,6 @@ void LineEdit::_notification(int p_what) { Ref<StyleBox> style = theme_cache.normal; if (!is_editable()) { style = theme_cache.read_only; - draw_caret = false; } Ref<Font> font = theme_cache.font; @@ -953,7 +948,7 @@ void LineEdit::_notification(int p_what) { // Draw carets. ofs.x = x_ofs + scroll_offset; - if (draw_caret || drag_caret_force_displayed) { + if ((caret_can_draw && draw_caret) || drag_caret_force_displayed) { // Prevent carets from disappearing at theme scales below 1.0 (if the caret width is 1). const int caret_width = theme_cache.caret_width * MAX(1, theme_cache.base_scale); @@ -1063,16 +1058,7 @@ void LineEdit::_notification(int p_what) { } break; case NOTIFICATION_FOCUS_ENTER: { - if (!caret_force_displayed) { - if (caret_blink_enabled) { - if (!caret_blinking) { - caret_blinking = true; - caret_blink_timer = 0.0; - } - } else { - draw_caret = true; - } - } + _validate_caret_can_draw(); if (select_all_on_focus) { if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { @@ -1093,9 +1079,7 @@ void LineEdit::_notification(int p_what) { } break; case NOTIFICATION_FOCUS_EXIT: { - if (caret_blink_enabled && !caret_force_displayed) { - caret_blinking = false; - } + _validate_caret_can_draw(); if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id()); @@ -1401,21 +1385,18 @@ bool LineEdit::is_caret_blink_enabled() const { } void LineEdit::set_caret_blink_enabled(const bool p_enabled) { + if (caret_blink_enabled == p_enabled) { + return; + } + caret_blink_enabled = p_enabled; set_process_internal(p_enabled); - if (has_focus() || caret_force_displayed) { - if (p_enabled) { - if (!caret_blinking) { - caret_blinking = true; - caret_blink_timer = 0.0; - } - } else { - caret_blinking = false; - } + draw_caret = !caret_blink_enabled; + if (caret_blink_enabled) { + caret_blink_timer = 0.0; } - - draw_caret = true; + queue_redraw(); notify_property_list_changed(); } @@ -1425,8 +1406,13 @@ bool LineEdit::is_caret_force_displayed() const { } void LineEdit::set_caret_force_displayed(const bool p_enabled) { + if (caret_force_displayed == p_enabled) { + return; + } + caret_force_displayed = p_enabled; - set_caret_blink_enabled(caret_blink_enabled); + _validate_caret_can_draw(); + queue_redraw(); } @@ -1442,7 +1428,7 @@ void LineEdit::set_caret_blink_interval(const float p_interval) { void LineEdit::_reset_caret_blink_timer() { if (caret_blink_enabled) { draw_caret = true; - if (has_focus()) { + if (caret_can_draw) { caret_blink_timer = 0.0; queue_redraw(); } @@ -1451,11 +1437,19 @@ void LineEdit::_reset_caret_blink_timer() { void LineEdit::_toggle_draw_caret() { draw_caret = !draw_caret; - if (is_visible_in_tree() && ((has_focus() && window_has_focus) || caret_force_displayed)) { + if (is_visible_in_tree() && caret_can_draw) { queue_redraw(); } } +void LineEdit::_validate_caret_can_draw() { + if (caret_blink_enabled) { + draw_caret = true; + caret_blink_timer = 0.0; + } + caret_can_draw = editable && (window_has_focus || (menu && menu->has_focus())) && (has_focus() || caret_force_displayed); +} + void LineEdit::delete_char() { if ((text.length() <= 0) || (caret_column == 0)) { return; @@ -1846,6 +1840,7 @@ void LineEdit::set_editable(bool p_editable) { } editable = p_editable; + _validate_caret_can_draw(); update_minimum_size(); queue_redraw(); @@ -2523,6 +2518,8 @@ void LineEdit::_ensure_menu() { menu->add_child(menu_ctl, false, INTERNAL_MODE_FRONT); menu->connect("id_pressed", callable_mp(this, &LineEdit::menu_option)); + menu->connect(SNAME("focus_entered"), callable_mp(this, &LineEdit::_validate_caret_can_draw)); + menu->connect(SNAME("focus_exited"), callable_mp(this, &LineEdit::_validate_caret_can_draw)); menu_dir->connect("id_pressed", callable_mp(this, &LineEdit::menu_option)); menu_ctl->connect("id_pressed", callable_mp(this, &LineEdit::menu_option)); } diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index e0a079b623..79db9dce21 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -172,7 +172,7 @@ private: bool draw_caret = true; float caret_blink_interval = 0.65; double caret_blink_timer = 0.0; - bool caret_blinking = false; + bool caret_can_draw = false; bool pending_select_all_on_focus = false; bool select_all_on_focus = false; @@ -225,6 +225,7 @@ private: void _reset_caret_blink_timer(); void _toggle_draw_caret(); + void _validate_caret_can_draw(); void clear_internal(); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index ab74979777..5d8e106e26 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -221,7 +221,7 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { Rect2 safe_area = this_rect; safe_area.position.y += items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2; - safe_area.size.y = items[p_over]._height_cache; + safe_area.size.y = items[p_over]._height_cache + theme_cache.v_separation; DisplayServer::get_singleton()->window_set_popup_safe_rect(submenu_popup->get_window_id(), safe_area); // Make the position of the parent popup relative to submenu popup. diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index e0e4ead55f..00c81c8616 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -85,7 +85,7 @@ void Range::set_value(double p_val) { void Range::set_value_no_signal(double p_val) { if (shared->step > 0) { - p_val = Math::round(p_val / shared->step) * shared->step; + p_val = Math::round((p_val - shared->min) / shared->step) * shared->step + shared->min; } if (_rounded_values) { diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index df41863e74..60d107cce6 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -4528,6 +4528,30 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } +void RichTextLabel::scroll_to_selection() { + if (selection.active && selection.from_frame && selection.from_line >= 0 && selection.from_line < (int)selection.from_frame->lines.size()) { + // Selected frame paragraph offset. + float line_offset = selection.from_frame->lines[selection.from_line].offset.y; + + // Add wrapped line offset. + for (int i = 0; i < selection.from_frame->lines[selection.from_line].text_buf->get_line_count(); i++) { + Vector2i range = selection.from_frame->lines[selection.from_line].text_buf->get_line_range(i); + if (range.x <= selection.from_char && range.y >= selection.from_char) { + break; + } + line_offset += selection.from_frame->lines[selection.from_line].text_buf->get_line_size(i).y + theme_cache.line_separation; + } + + // Add nested frame (e.g. table cell) offset. + ItemFrame *it = selection.from_frame; + while (it->parent_frame != nullptr) { + line_offset += it->parent_frame->lines[it->line].offset.y; + it = it->parent_frame; + } + vscroll->set_value(line_offset); + } +} + void RichTextLabel::scroll_to_paragraph(int p_paragraph) { _validate_line_caches(); @@ -4772,7 +4796,7 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p char_idx = p_search_previous ? selection.from_char - 1 : selection.to_char; if (!(p_search_previous && char_idx < 0) && _search_line(selection.from_frame, selection.from_line, p_string, char_idx, p_search_previous)) { - scroll_to_line(selection.from_frame->line + selection.from_line); + scroll_to_selection(); queue_redraw(); return true; } @@ -4797,7 +4821,7 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p // Search for next element if (_search_table(parent_table, parent_element, p_string, p_search_previous)) { - scroll_to_line(selection.from_frame->line + selection.from_line); + scroll_to_selection(); queue_redraw(); return true; } @@ -4821,7 +4845,7 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p } if (_search_line(main, current_line, p_string, char_idx, p_search_previous)) { - scroll_to_line(current_line); + scroll_to_selection(); queue_redraw(); return true; } @@ -5309,6 +5333,7 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("scroll_to_line", "line"), &RichTextLabel::scroll_to_line); ClassDB::bind_method(D_METHOD("scroll_to_paragraph", "paragraph"), &RichTextLabel::scroll_to_paragraph); + ClassDB::bind_method(D_METHOD("scroll_to_selection"), &RichTextLabel::scroll_to_selection); ClassDB::bind_method(D_METHOD("set_tab_size", "spaces"), &RichTextLabel::set_tab_size); ClassDB::bind_method(D_METHOD("get_tab_size"), &RichTextLabel::get_tab_size); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index d30baaa8d3..b00cc3d055 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -657,6 +657,8 @@ public: int get_content_height() const; int get_content_width() const; + void scroll_to_selection(); + VScrollBar *get_v_scroll_bar() { return vscroll; } virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override; diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 6899178885..a44ddff507 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -550,7 +550,7 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) { drag_node_accum = Vector2(); last_drag_node_accum = Vector2(); drag_node_from = Vector2(orientation == HORIZONTAL ? get_value() : 0, orientation == VERTICAL ? get_value() : 0); - drag_node_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())); + drag_node_touching = DisplayServer::get_singleton()->is_touchscreen_available(); drag_node_touching_deaccel = false; time_since_motion = 0; diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 531226f938..73d30b7568 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -164,8 +164,8 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { } } - bool screen_is_touchscreen = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())); - if (!screen_is_touchscreen) { + bool is_touchscreen_available = DisplayServer::get_singleton()->is_touchscreen_available(); + if (!is_touchscreen_available) { return; } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index cce9fa4f34..a96d85dffa 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1108,8 +1108,9 @@ void TextEdit::_notification(int p_what) { int start = TS->shaped_text_get_range(rid).x; if (!clipped && !search_text.is_empty()) { // Search highhlight int search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0); + int search_text_len = search_text.length(); while (search_text_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text.length() + start); + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1125,14 +1126,15 @@ void TextEdit::_notification(int p_what) { draw_rect(rect, search_result_border_color, false); } - search_text_col = _get_column_pos_of_word(search_text, str, search_flags, search_text_col + 1); + search_text_col = _get_column_pos_of_word(search_text, str, search_flags, search_text_col + search_text_len); } } if (!clipped && highlight_all_occurrences && !only_whitespaces_highlighted && !highlighted_text.is_empty()) { // Highlight int highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + int highlighted_text_len = highlighted_text.length(); while (highlighted_text_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text.length() + start); + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1147,15 +1149,16 @@ void TextEdit::_notification(int p_what) { draw_rect(rect, word_highlighted_color); } - highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_text_col + 1); + highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_text_col + highlighted_text_len); } } if (!clipped && lookup_symbol_word.length() != 0) { // Highlight word if (is_ascii_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '_' || lookup_symbol_word[0] == '.') { - int highlighted_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); - while (highlighted_word_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_word_col + start, highlighted_word_col + lookup_symbol_word.length() + start); + int lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + int lookup_symbol_word_len = lookup_symbol_word.length(); + while (lookup_symbol_word_col != -1) { + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, lookup_symbol_word_col + start, lookup_symbol_word_col + lookup_symbol_word_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y + (line_spacing / 2), sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1172,7 +1175,7 @@ void TextEdit::_notification(int p_what) { draw_rect(rect, color); } - highlighted_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_word_col + 1); + lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, lookup_symbol_word_col + lookup_symbol_word_len); } } } @@ -7038,8 +7041,8 @@ void TextEdit::_update_selection_mode_word() { if ((col <= carets[caret_idx].selection.selected_word_origin && line == get_selection_line(caret_idx)) || line < get_selection_line(caret_idx)) { carets.write[caret_idx].selection.selecting_column = carets[caret_idx].selection.selected_word_end; select(line, beg, get_selection_line(caret_idx), carets[caret_idx].selection.selected_word_end, caret_idx); - set_caret_line(get_selection_from_line(caret_idx), false, true, 0, caret_idx); - set_caret_column(get_selection_from_column(caret_idx), true, caret_idx); + set_caret_line(line, false, true, 0, caret_idx); + set_caret_column(beg, true, caret_idx); } else { carets.write[caret_idx].selection.selecting_column = carets[caret_idx].selection.selected_word_beg; select(get_selection_line(caret_idx), carets[caret_idx].selection.selected_word_beg, line, end, caret_idx); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 93c910a7f0..3996d2d65d 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -1018,7 +1018,7 @@ void TreeItem::set_as_cursor(int p_column) { if (tree->select_mode != Tree::SELECT_MULTI) { return; } - if (tree->selected_col == p_column) { + if (tree->selected_item == this && tree->selected_col == p_column) { return; } tree->selected_item = this; @@ -3671,7 +3671,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { drag_accum = 0; //last_drag_accum=0; drag_from = v_scroll->get_value(); - drag_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())); + drag_touching = DisplayServer::get_singleton()->is_touchscreen_available(); drag_touching_deaccel = false; if (drag_touching) { set_physics_process_internal(true); diff --git a/scene/main/window.cpp b/scene/main/window.cpp index aff2c594d9..73436b1658 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -106,9 +106,16 @@ void Window::reset_size() { set_size(Size2i()); } -Size2i Window::get_real_size() const { +Point2i Window::get_position_with_decorations() const { if (window_id != DisplayServer::INVALID_WINDOW_ID) { - return DisplayServer::get_singleton()->window_get_real_size(window_id); + return DisplayServer::get_singleton()->window_get_position_with_decorations(window_id); + } + return position; +} + +Size2i Window::get_size_with_decorations() const { + if (window_id != DisplayServer::INVALID_WINDOW_ID) { + return DisplayServer::get_singleton()->window_get_size_with_decorations(window_id); } return size; } @@ -1655,7 +1662,8 @@ void Window::_bind_methods() { ClassDB::bind_method(D_METHOD("get_size"), &Window::get_size); ClassDB::bind_method(D_METHOD("reset_size"), &Window::reset_size); - ClassDB::bind_method(D_METHOD("get_real_size"), &Window::get_real_size); + ClassDB::bind_method(D_METHOD("get_position_with_decorations"), &Window::get_position_with_decorations); + ClassDB::bind_method(D_METHOD("get_size_with_decorations"), &Window::get_size_with_decorations); ClassDB::bind_method(D_METHOD("set_max_size", "max_size"), &Window::set_max_size); ClassDB::bind_method(D_METHOD("get_max_size"), &Window::get_max_size); diff --git a/scene/main/window.h b/scene/main/window.h index 03597b309a..48ce9041a4 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -198,7 +198,8 @@ public: Size2i get_size() const; void reset_size(); - Size2i get_real_size() const; + Point2i get_position_with_decorations() const; + Size2i get_size_with_decorations() const; void set_max_size(const Size2i &p_max_size); Size2i get_max_size() const; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index ee45a8ea6f..58147b159a 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -827,14 +827,14 @@ void register_scene_types() { GDREGISTER_CLASS(ConvexPolygonShape3D); GDREGISTER_CLASS(ConcavePolygonShape3D); - ClassDB::register_class<SkeletonModificationStack3D>(); - ClassDB::register_class<SkeletonModification3D>(); - ClassDB::register_class<SkeletonModification3DLookAt>(); - ClassDB::register_class<SkeletonModification3DCCDIK>(); - ClassDB::register_class<SkeletonModification3DFABRIK>(); - ClassDB::register_class<SkeletonModification3DJiggle>(); - ClassDB::register_class<SkeletonModification3DTwoBoneIK>(); - ClassDB::register_class<SkeletonModification3DStackHolder>(); + GDREGISTER_CLASS(SkeletonModificationStack3D); + GDREGISTER_CLASS(SkeletonModification3D); + GDREGISTER_CLASS(SkeletonModification3DLookAt); + GDREGISTER_CLASS(SkeletonModification3DCCDIK); + GDREGISTER_CLASS(SkeletonModification3DFABRIK); + GDREGISTER_CLASS(SkeletonModification3DJiggle); + GDREGISTER_CLASS(SkeletonModification3DTwoBoneIK); + GDREGISTER_CLASS(SkeletonModification3DStackHolder); OS::get_singleton()->yield(); // may take time to init #endif // _3D_DISABLED diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 3967858d47..077a53464e 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -2709,7 +2709,7 @@ void Animation::value_track_set_update_mode(int p_track, UpdateMode p_mode) { ERR_FAIL_INDEX(p_track, tracks.size()); Track *t = tracks[p_track]; ERR_FAIL_COND(t->type != TYPE_VALUE); - ERR_FAIL_INDEX((int)p_mode, 4); + ERR_FAIL_INDEX((int)p_mode, 3); ValueTrack *vt = static_cast<ValueTrack *>(t); vt->update_mode = p_mode; @@ -3896,7 +3896,6 @@ void Animation::_bind_methods() { BIND_ENUM_CONSTANT(UPDATE_CONTINUOUS); BIND_ENUM_CONSTANT(UPDATE_DISCRETE); - BIND_ENUM_CONSTANT(UPDATE_TRIGGER); BIND_ENUM_CONSTANT(UPDATE_CAPTURE); BIND_ENUM_CONSTANT(LOOP_NONE); diff --git a/scene/resources/animation.h b/scene/resources/animation.h index e66af77018..0ac1279063 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -64,7 +64,6 @@ public: enum UpdateMode { UPDATE_CONTINUOUS, UPDATE_DISCRETE, - UPDATE_TRIGGER, UPDATE_CAPTURE, }; diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index 93c3d4f851..bc2149a8c6 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -341,7 +341,7 @@ real_t Curve::sample_local_nocheck(int p_index, real_t p_local_offset) const { const Point a = _points[p_index]; const Point b = _points[p_index + 1]; - /* Cubic bezier + /* Cubic bézier * * ac-----bc * / \ @@ -774,6 +774,22 @@ void Curve2D::_bake_segment2d(RBMap<real_t, Vector2> &r_bake, real_t p_begin, re } } +void Curve2D::_bake_segment2d_even_length(RBMap<real_t, Vector2> &r_bake, real_t p_begin, real_t p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_max_depth, real_t p_length) const { + Vector2 beg = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_begin); + Vector2 end = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, p_end); + + real_t length = beg.distance_to(end); + + if (length > p_length && p_depth < p_max_depth) { + real_t mp = (p_begin + p_end) * 0.5; + Vector2 mid = p_a.bezier_interpolate(p_a + p_out, p_b + p_in, p_b, mp); + r_bake[mp] = mid; + + _bake_segment2d_even_length(r_bake, p_begin, mp, p_a, p_out, p_b, p_in, p_depth + 1, p_max_depth, p_length); + _bake_segment2d_even_length(r_bake, mp, p_end, p_a, p_out, p_b, p_in, p_depth + 1, p_max_depth, p_length); + } +} + void Curve2D::_bake() const { if (!baked_cache_dirty) { return; @@ -785,94 +801,62 @@ void Curve2D::_bake() const { if (points.size() == 0) { baked_point_cache.clear(); baked_dist_cache.clear(); + baked_forward_vector_cache.clear(); return; } if (points.size() == 1) { baked_point_cache.resize(1); baked_point_cache.set(0, points[0].position); - baked_dist_cache.resize(1); baked_dist_cache.set(0, 0.0); + baked_forward_vector_cache.resize(1); + baked_forward_vector_cache.set(0, Vector2(0.0, 0.1)); + return; } - Vector2 position = points[0].position; - real_t dist = 0.0; - - List<Vector2> pointlist; - List<real_t> distlist; - - // Start always from origin. - pointlist.push_back(position); - distlist.push_back(0.0); - - for (int i = 0; i < points.size() - 1; i++) { - real_t step = 0.1; // at least 10 substeps ought to be enough? - real_t p = 0.0; - - while (p < 1.0) { - real_t np = p + step; - if (np > 1.0) { - np = 1.0; - } - - Vector2 npp = points[i].position.bezier_interpolate(points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, np); - real_t d = position.distance_to(npp); - - if (d > bake_interval) { - // OK! between P and NP there _has_ to be Something, let's go searching! - - int iterations = 10; //lots of detail! - - real_t low = p; - real_t hi = np; - real_t mid = low + (hi - low) * 0.5; - - for (int j = 0; j < iterations; j++) { - npp = points[i].position.bezier_interpolate(points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, mid); - d = position.distance_to(npp); - - if (bake_interval < d) { - hi = mid; - } else { - low = mid; - } - mid = low + (hi - low) * 0.5; - } - - position = npp; - p = mid; - dist += d; + // Tesselate curve to (almost) even length segments + { + Vector<RBMap<real_t, Vector2>> midpoints = _tessellate_even_length(10, bake_interval); - pointlist.push_back(position); - distlist.push_back(dist); - } else { - p = np; - } + int pc = 1; + for (int i = 0; i < points.size() - 1; i++) { + pc++; + pc += midpoints[i].size(); } - Vector2 npp = points[i + 1].position; - real_t d = position.distance_to(npp); + baked_point_cache.resize(pc); + baked_dist_cache.resize(pc); + baked_forward_vector_cache.resize(pc); - position = npp; - dist += d; + Vector2 *bpw = baked_point_cache.ptrw(); + Vector2 *bfw = baked_forward_vector_cache.ptrw(); - pointlist.push_back(position); - distlist.push_back(dist); - } - - baked_max_ofs = dist; + // Collect positions and sample tilts and tangents for each baked points. + bpw[0] = points[0].position; + bfw[0] = points[0].position.bezier_derivative(points[0].position + points[0].out, points[1].position + points[1].in, points[1].position, 0.0).normalized(); + int pidx = 0; - baked_point_cache.resize(pointlist.size()); - baked_dist_cache.resize(distlist.size()); + for (int i = 0; i < points.size() - 1; i++) { + for (const KeyValue<real_t, Vector2> &E : midpoints[i]) { + pidx++; + bpw[pidx] = E.value; + bfw[pidx] = points[i].position.bezier_derivative(points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, E.key).normalized(); + } - Vector2 *w = baked_point_cache.ptrw(); - real_t *wd = baked_dist_cache.ptrw(); + pidx++; + bpw[pidx] = points[i + 1].position; + bfw[pidx] = points[i].position.bezier_derivative(points[i].position + points[i].out, points[i + 1].position + points[i + 1].in, points[i + 1].position, 1.0).normalized(); + } - for (int i = 0; i < pointlist.size(); i++) { - w[i] = pointlist[i]; - wd[i] = distlist[i]; + // Recalculate the baked distances. + real_t *bdw = baked_dist_cache.ptrw(); + bdw[0] = 0.0; + for (int i = 0; i < pc - 1; i++) { + bdw[i + 1] = bdw[i] + bpw[i].distance_to(bpw[i + 1]); + } + baked_max_ofs = bdw[pc - 1]; } } @@ -884,27 +868,15 @@ real_t Curve2D::get_baked_length() const { return baked_max_ofs; } -Vector2 Curve2D::sample_baked(real_t p_offset, bool p_cubic) const { - if (baked_cache_dirty) { - _bake(); - } +Curve2D::Interval Curve2D::_find_interval(real_t p_offset) const { + Interval interval = { + -1, + 0.0 + }; + ERR_FAIL_COND_V_MSG(baked_cache_dirty, interval, "Backed cache is dirty"); - // Validate: Curve may not have baked points. int pc = baked_point_cache.size(); - ERR_FAIL_COND_V_MSG(pc == 0, Vector2(), "No points in Curve2D."); - - if (pc == 1) { - return baked_point_cache.get(0); - } - - const Vector2 *r = baked_point_cache.ptr(); - - if (p_offset < 0) { - return r[0]; - } - if (p_offset >= baked_max_ofs) { - return r[pc - 1]; - } + ERR_FAIL_COND_V_MSG(pc < 2, interval, "Less than two points in cache"); int start = 0; int end = pc; @@ -924,9 +896,27 @@ Vector2 Curve2D::sample_baked(real_t p_offset, bool p_cubic) const { real_t offset_end = baked_dist_cache[idx + 1]; real_t idx_interval = offset_end - offset_begin; - ERR_FAIL_COND_V_MSG(p_offset < offset_begin || p_offset > offset_end, Vector2(), "Couldn't find baked segment."); + ERR_FAIL_COND_V_MSG(p_offset < offset_begin || p_offset > offset_end, interval, "Offset out of range."); - real_t frac = (p_offset - offset_begin) / idx_interval; + interval.idx = idx; + if (idx_interval < FLT_EPSILON) { + interval.frac = 0.5; // For a very short interval, 0.5 is a reasonable choice. + ERR_FAIL_V_MSG(interval, "Zero length interval."); + } + + interval.frac = (p_offset - offset_begin) / idx_interval; + return interval; +} + +Vector2 Curve2D::_sample_baked(Interval p_interval, bool p_cubic) const { + // Assuming p_interval is valid. + ERR_FAIL_INDEX_V_MSG(p_interval.idx, baked_point_cache.size(), Vector2(), "Invalid interval"); + + int idx = p_interval.idx; + real_t frac = p_interval.frac; + + const Vector2 *r = baked_point_cache.ptr(); + int pc = baked_point_cache.size(); if (p_cubic) { Vector2 pre = idx > 0 ? r[idx - 1] : r[idx]; @@ -937,44 +927,70 @@ Vector2 Curve2D::sample_baked(real_t p_offset, bool p_cubic) const { } } -Transform2D Curve2D::sample_baked_with_rotation(real_t p_offset, bool p_cubic, bool p_loop, real_t p_lookahead) const { - real_t path_length = get_baked_length(); // Ensure baked. - ERR_FAIL_COND_V_MSG(path_length == 0, Transform2D(), "Length of Curve2D is 0."); +Transform2D Curve2D::_sample_posture(Interval p_interval) const { + // Assuming that p_interval is valid. + ERR_FAIL_INDEX_V_MSG(p_interval.idx, baked_point_cache.size(), Transform2D(), "Invalid interval"); - Vector2 pos = sample_baked(p_offset, p_cubic); + int idx = p_interval.idx; + real_t frac = p_interval.frac; - real_t ahead = p_offset + p_lookahead; + Vector2 forward_begin = baked_forward_vector_cache[idx]; + Vector2 forward_end = baked_forward_vector_cache[idx + 1]; - if (p_loop && ahead >= path_length) { - // If our lookahead will loop, we need to check if the path is closed. - int point_count = get_point_count(); - if (point_count > 0) { - Vector2 start_point = get_point_position(0); - Vector2 end_point = get_point_position(point_count - 1); - if (start_point == end_point) { - // Since the path is closed we want to 'smooth off' - // the corner at the start/end. - // So we wrap the lookahead back round. - ahead = Math::fmod(ahead, path_length); - } - } + // Build frames at both ends of the interval, then interpolate. + const Vector2 forward = forward_begin.slerp(forward_end, frac).normalized(); + const Vector2 side = Vector2(-forward.y, forward.x); + + return Transform2D(side, forward, Vector2(0.0, 0.0)); +} + +Vector2 Curve2D::sample_baked(real_t p_offset, bool p_cubic) const { + if (baked_cache_dirty) { + _bake(); } - Vector2 ahead_pos = sample_baked(ahead, p_cubic); + // Validate: Curve may not have baked points. + int pc = baked_point_cache.size(); + ERR_FAIL_COND_V_MSG(pc == 0, Vector2(), "No points in Curve2D."); - Vector2 tangent_to_curve; - if (ahead_pos == pos) { - // This will happen at the end of non-looping or non-closed paths. - // We'll try a look behind instead, in order to get a meaningful angle. - tangent_to_curve = - (pos - sample_baked(p_offset - p_lookahead, p_cubic)).normalized(); - } else { - tangent_to_curve = (ahead_pos - pos).normalized(); + if (pc == 1) { + return baked_point_cache[0]; + } + + p_offset = CLAMP(p_offset, 0.0, get_baked_length()); // PathFollower implement wrapping logic. + + Curve2D::Interval interval = _find_interval(p_offset); + return _sample_baked(interval, p_cubic); +} + +Transform2D Curve2D::sample_baked_with_rotation(real_t p_offset, bool p_cubic) const { + if (baked_cache_dirty) { + _bake(); } - Vector2 normal_of_curve = -tangent_to_curve.orthogonal(); + // Validate: Curve may not have baked points. + const int point_count = baked_point_cache.size(); + ERR_FAIL_COND_V_MSG(point_count == 0, Transform2D(), "No points in Curve3D."); + + if (point_count == 1) { + Transform2D t; + t.set_origin(baked_point_cache.get(0)); + ERR_FAIL_V_MSG(t, "Only 1 point in Curve2D."); + } + + p_offset = CLAMP(p_offset, 0.0, get_baked_length()); // PathFollower implement wrapping logic. + + // 0. Find interval for all sampling steps. + Curve2D::Interval interval = _find_interval(p_offset); + + // 1. Sample position. + Vector2 pos = _sample_baked(interval, p_cubic); - return Transform2D(normal_of_curve, tangent_to_curve, pos); + // 2. Sample rotation frame. + Transform2D frame = _sample_posture(interval); + frame.set_origin(pos); + + return frame; } PackedVector2Array Curve2D::get_baked_points() const { @@ -1147,6 +1163,50 @@ PackedVector2Array Curve2D::tessellate(int p_max_stages, real_t p_tolerance) con return tess; } +Vector<RBMap<real_t, Vector2>> Curve2D::_tessellate_even_length(int p_max_stages, real_t p_length) const { + Vector<RBMap<real_t, Vector2>> midpoints; + ERR_FAIL_COND_V_MSG(points.size() < 2, midpoints, "Curve must have at least 2 control point"); + + midpoints.resize(points.size() - 1); + + for (int i = 0; i < points.size() - 1; i++) { + _bake_segment2d_even_length(midpoints.write[i], 0, 1, points[i].position, points[i].out, points[i + 1].position, points[i + 1].in, 0, p_max_stages, p_length); + } + return midpoints; +} + +PackedVector2Array Curve2D::tessellate_even_length(int p_max_stages, real_t p_length) const { + PackedVector2Array tess; + + Vector<RBMap<real_t, Vector2>> midpoints = _tessellate_even_length(p_max_stages, p_length); + if (midpoints.size() == 0) { + return tess; + } + + int pc = 1; + for (int i = 0; i < points.size() - 1; i++) { + pc++; + pc += midpoints[i].size(); + } + + tess.resize(pc); + Vector2 *bpw = tess.ptrw(); + bpw[0] = points[0].position; + int pidx = 0; + + for (int i = 0; i < points.size() - 1; i++) { + for (const KeyValue<real_t, Vector2> &E : midpoints[i]) { + pidx++; + bpw[pidx] = E.value; + } + + pidx++; + bpw[pidx] = points[i + 1].position; + } + + return tess; +} + bool Curve2D::_set(const StringName &p_name, const Variant &p_value) { Vector<String> components = String(p_name).split("/", true, 2); if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) { @@ -1224,12 +1284,13 @@ void Curve2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bake_interval"), &Curve2D::get_bake_interval); ClassDB::bind_method(D_METHOD("get_baked_length"), &Curve2D::get_baked_length); - ClassDB::bind_method(D_METHOD("sample_baked", "offset", "cubic"), &Curve2D::sample_baked, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("sample_baked_with_rotation", "offset", "cubic", "loop", "lookahead"), &Curve2D::sample_baked_with_rotation, DEFVAL(false), DEFVAL(true), DEFVAL(4.0)); + ClassDB::bind_method(D_METHOD("sample_baked", "offset", "cubic"), &Curve2D::sample_baked, DEFVAL(0.0), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("sample_baked_with_rotation", "offset", "cubic"), &Curve2D::sample_baked_with_rotation, DEFVAL(0.0), DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_baked_points"), &Curve2D::get_baked_points); ClassDB::bind_method(D_METHOD("get_closest_point", "to_point"), &Curve2D::get_closest_point); ClassDB::bind_method(D_METHOD("get_closest_offset", "to_point"), &Curve2D::get_closest_offset); ClassDB::bind_method(D_METHOD("tessellate", "max_stages", "tolerance_degrees"), &Curve2D::tessellate, DEFVAL(5), DEFVAL(4)); + ClassDB::bind_method(D_METHOD("tessellate_even_length", "max_stages", "tolerance_length"), &Curve2D::tessellate_even_length, DEFVAL(5), DEFVAL(20.0)); ClassDB::bind_method(D_METHOD("_get_data"), &Curve2D::_get_data); ClassDB::bind_method(D_METHOD("_set_data", "data"), &Curve2D::_set_data); @@ -2133,8 +2194,8 @@ void Curve3D::_bind_methods() { ClassDB::bind_method(D_METHOD("is_up_vector_enabled"), &Curve3D::is_up_vector_enabled); ClassDB::bind_method(D_METHOD("get_baked_length"), &Curve3D::get_baked_length); - ClassDB::bind_method(D_METHOD("sample_baked", "offset", "cubic"), &Curve3D::sample_baked, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("sample_baked_with_rotation", "offset", "cubic", "apply_tilt"), &Curve3D::sample_baked_with_rotation, DEFVAL(false), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("sample_baked", "offset", "cubic"), &Curve3D::sample_baked, DEFVAL(0.0), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("sample_baked_with_rotation", "offset", "cubic", "apply_tilt"), &Curve3D::sample_baked_with_rotation, DEFVAL(0.0), DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("sample_baked_up_vector", "offset", "apply_tilt"), &Curve3D::sample_baked_up_vector, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_baked_points"), &Curve3D::get_baked_points); ClassDB::bind_method(D_METHOD("get_baked_tilts"), &Curve3D::get_baked_tilts); diff --git a/scene/resources/curve.h b/scene/resources/curve.h index 88c3cf3ae6..ea3ceabb14 100644 --- a/scene/resources/curve.h +++ b/scene/resources/curve.h @@ -172,6 +172,7 @@ class Curve2D : public Resource { mutable bool baked_cache_dirty = false; mutable PackedVector2Array baked_point_cache; + mutable PackedVector2Array baked_forward_vector_cache; mutable Vector<real_t> baked_dist_cache; mutable real_t baked_max_ofs = 0.0; @@ -181,7 +182,16 @@ class Curve2D : public Resource { real_t bake_interval = 5.0; + struct Interval { + int idx; + real_t frac; + }; + Interval _find_interval(real_t p_offset) const; + Vector2 _sample_baked(Interval p_interval, bool p_cubic) const; + Transform2D _sample_posture(Interval p_interval) const; + void _bake_segment2d(RBMap<real_t, Vector2> &r_bake, real_t p_begin, real_t p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_max_depth, real_t p_tol) const; + void _bake_segment2d_even_length(RBMap<real_t, Vector2> &r_bake, real_t p_begin, real_t p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_max_depth, real_t p_length) const; Dictionary _get_data() const; void _set_data(const Dictionary &p_data); @@ -192,6 +202,8 @@ class Curve2D : public Resource { void _add_point(const Vector2 &p_position, const Vector2 &p_in = Vector2(), const Vector2 &p_out = Vector2(), int p_atpos = -1); void _remove_point(int p_index); + Vector<RBMap<real_t, Vector2>> _tessellate_even_length(int p_max_stages = 5, real_t p_length = 0.2) const; + protected: static void _bind_methods(); @@ -216,12 +228,13 @@ public: real_t get_baked_length() const; Vector2 sample_baked(real_t p_offset, bool p_cubic = false) const; - Transform2D sample_baked_with_rotation(real_t p_offset, bool p_cubic = false, bool p_loop = true, real_t p_lookahead = 4.0) const; + Transform2D sample_baked_with_rotation(real_t p_offset, bool p_cubic = false) const; PackedVector2Array get_baked_points() const; //useful for going through Vector2 get_closest_point(const Vector2 &p_to_point) const; real_t get_closest_offset(const Vector2 &p_to_point) const; PackedVector2Array tessellate(int p_max_stages = 5, real_t p_tolerance = 4) const; //useful for display + PackedVector2Array tessellate_even_length(int p_max_stages = 5, real_t p_length = 20.0) const; // Useful for baking. Curve2D(); }; diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index af51d6539e..584a7e7eac 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -63,6 +63,8 @@ void Font::_bind_methods() { ClassDB::bind_method(D_METHOD("get_font_name"), &Font::get_font_name); ClassDB::bind_method(D_METHOD("get_font_style_name"), &Font::get_font_style_name); ClassDB::bind_method(D_METHOD("get_font_style"), &Font::get_font_style); + ClassDB::bind_method(D_METHOD("get_font_weight"), &Font::get_font_weight); + ClassDB::bind_method(D_METHOD("get_font_stretch"), &Font::get_font_stretch); ClassDB::bind_method(D_METHOD("get_spacing", "spacing"), &Font::get_spacing); ClassDB::bind_method(D_METHOD("get_opentype_features"), &Font::get_opentype_features); @@ -249,6 +251,14 @@ BitField<TextServer::FontStyle> Font::get_font_style() const { return TS->font_get_style(_get_rid()); } +int Font::get_font_weight() const { + return TS->font_get_weight(_get_rid()); +} + +int Font::get_font_stretch() const { + return TS->font_get_stretch(_get_rid()); +} + Dictionary Font::get_opentype_features() const { return Dictionary(); } @@ -590,6 +600,7 @@ _FORCE_INLINE_ void FontFile::_ensure_rid(int p_cache_index) const { TS->font_set_msdf_size(cache[p_cache_index], msdf_size); TS->font_set_fixed_size(cache[p_cache_index], fixed_size); TS->font_set_force_autohinter(cache[p_cache_index], force_autohinter); + TS->font_set_allow_system_fallback(cache[p_cache_index], allow_system_fallback); TS->font_set_hinting(cache[p_cache_index], hinting); TS->font_set_subpixel_positioning(cache[p_cache_index], subpixel_positioning); TS->font_set_oversampling(cache[p_cache_index], oversampling); @@ -881,6 +892,8 @@ void FontFile::_bind_methods() { ClassDB::bind_method(D_METHOD("set_font_name", "name"), &FontFile::set_font_name); ClassDB::bind_method(D_METHOD("set_font_style_name", "name"), &FontFile::set_font_style_name); ClassDB::bind_method(D_METHOD("set_font_style", "style"), &FontFile::set_font_style); + ClassDB::bind_method(D_METHOD("set_font_weight", "weight"), &FontFile::set_font_weight); + ClassDB::bind_method(D_METHOD("set_font_stretch", "stretch"), &FontFile::set_font_stretch); ClassDB::bind_method(D_METHOD("set_antialiasing", "antialiasing"), &FontFile::set_antialiasing); ClassDB::bind_method(D_METHOD("get_antialiasing"), &FontFile::get_antialiasing); @@ -900,6 +913,9 @@ void FontFile::_bind_methods() { ClassDB::bind_method(D_METHOD("set_fixed_size", "fixed_size"), &FontFile::set_fixed_size); ClassDB::bind_method(D_METHOD("get_fixed_size"), &FontFile::get_fixed_size); + ClassDB::bind_method(D_METHOD("set_allow_system_fallback", "allow_system_fallback"), &FontFile::set_allow_system_fallback); + ClassDB::bind_method(D_METHOD("is_allow_system_fallback"), &FontFile::is_allow_system_fallback); + ClassDB::bind_method(D_METHOD("set_force_autohinter", "force_autohinter"), &FontFile::set_force_autohinter); ClassDB::bind_method(D_METHOD("is_force_autohinter"), &FontFile::is_force_autohinter); @@ -1007,10 +1023,14 @@ void FontFile::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "font_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_font_name", "get_font_name"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "style_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_font_style_name", "get_font_style_name"); ADD_PROPERTY(PropertyInfo(Variant::INT, "font_style", PROPERTY_HINT_FLAGS, "Bold,Italic,Fixed Size", PROPERTY_USAGE_STORAGE), "set_font_style", "get_font_style"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "font_weight", PROPERTY_HINT_RANGE, "100,999,25", PROPERTY_USAGE_STORAGE), "set_font_weight", "get_font_weight"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "font_stretch", PROPERTY_HINT_RANGE, "50,200,25", PROPERTY_USAGE_STORAGE), "set_font_stretch", "get_font_stretch"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One Half of a Pixel,One Quarter of a Pixel", PROPERTY_USAGE_STORAGE), "set_subpixel_positioning", "get_subpixel_positioning"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_multichannel_signed_distance_field", "is_multichannel_signed_distance_field"); ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_msdf_pixel_range", "get_msdf_pixel_range"); ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_msdf_size", "get_msdf_size"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_system_fallback", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_allow_system_fallback", "is_allow_system_fallback"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "force_autohinter", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_force_autohinter", "is_force_autohinter"); ADD_PROPERTY(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_STORAGE), "set_hinting", "get_hinting"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_oversampling", "get_oversampling"); @@ -1329,6 +1349,7 @@ void FontFile::reset_state() { mipmaps = false; msdf = false; force_autohinter = false; + allow_system_fallback = true; hinting = TextServer::HINTING_LIGHT; subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_DISABLED; msdf_pixel_range = 14; @@ -1361,6 +1382,7 @@ Error FontFile::load_bitmap_font(const String &p_path) { mipmaps = false; msdf = false; force_autohinter = false; + allow_system_fallback = true; hinting = TextServer::HINTING_NONE; oversampling = 1.0f; @@ -1937,6 +1959,9 @@ Error FontFile::load_bitmap_font(const String &p_path) { set_font_name(font_name); set_font_style(st_flags); + if (st_flags & TextServer::FONT_BOLD) { + set_font_weight(700); + } set_cache_ascent(0, base_size, ascent); set_cache_descent(0, base_size, height - ascent); @@ -1946,7 +1971,7 @@ Error FontFile::load_bitmap_font(const String &p_path) { Error FontFile::load_dynamic_font(const String &p_path) { reset_state(); - Vector<uint8_t> font_data = FileAccess::get_file_as_array(p_path); + Vector<uint8_t> font_data = FileAccess::get_file_as_bytes(p_path); set_data(font_data); return OK; @@ -2000,6 +2025,16 @@ void FontFile::set_font_style(BitField<TextServer::FontStyle> p_style) { TS->font_set_style(cache[0], p_style); } +void FontFile::set_font_weight(int p_weight) { + _ensure_rid(0); + TS->font_set_weight(cache[0], p_weight); +} + +void FontFile::set_font_stretch(int p_stretch) { + _ensure_rid(0); + TS->font_set_stretch(cache[0], p_stretch); +} + void FontFile::set_antialiasing(TextServer::FontAntialiasing p_antialiasing) { if (antialiasing != p_antialiasing) { antialiasing = p_antialiasing; @@ -2090,6 +2125,21 @@ int FontFile::get_fixed_size() const { return fixed_size; } +void FontFile::set_allow_system_fallback(bool p_allow_system_fallback) { + if (allow_system_fallback != p_allow_system_fallback) { + allow_system_fallback = p_allow_system_fallback; + for (int i = 0; i < cache.size(); i++) { + _ensure_rid(i); + TS->font_set_allow_system_fallback(cache[i], allow_system_fallback); + } + emit_changed(); + } +} + +bool FontFile::is_allow_system_fallback() const { + return allow_system_fallback; +} + void FontFile::set_force_autohinter(bool p_force_autohinter) { if (force_autohinter != p_force_autohinter) { force_autohinter = p_force_autohinter; @@ -2839,6 +2889,9 @@ void SystemFont::_bind_methods() { ClassDB::bind_method(D_METHOD("set_generate_mipmaps", "generate_mipmaps"), &SystemFont::set_generate_mipmaps); ClassDB::bind_method(D_METHOD("get_generate_mipmaps"), &SystemFont::get_generate_mipmaps); + ClassDB::bind_method(D_METHOD("set_allow_system_fallback", "allow_system_fallback"), &SystemFont::set_allow_system_fallback); + ClassDB::bind_method(D_METHOD("is_allow_system_fallback"), &SystemFont::is_allow_system_fallback); + ClassDB::bind_method(D_METHOD("set_force_autohinter", "force_autohinter"), &SystemFont::set_force_autohinter); ClassDB::bind_method(D_METHOD("is_force_autohinter"), &SystemFont::is_force_autohinter); @@ -2857,12 +2910,18 @@ void SystemFont::_bind_methods() { ClassDB::bind_method(D_METHOD("get_font_names"), &SystemFont::get_font_names); ClassDB::bind_method(D_METHOD("set_font_names", "names"), &SystemFont::set_font_names); - ClassDB::bind_method(D_METHOD("set_font_style", "style"), &SystemFont::set_font_style); + ClassDB::bind_method(D_METHOD("get_font_italic"), &SystemFont::get_font_italic); + ClassDB::bind_method(D_METHOD("set_font_italic", "italic"), &SystemFont::set_font_italic); + ClassDB::bind_method(D_METHOD("set_font_weight", "weight"), &SystemFont::set_font_weight); + ClassDB::bind_method(D_METHOD("set_font_stretch", "stretch"), &SystemFont::set_font_stretch); ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "font_names"), "set_font_names", "get_font_names"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "font_style", PROPERTY_HINT_FLAGS, "Bold,Italic"), "set_font_style", "get_font_style"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "font_italic"), "set_font_italic", "get_font_italic"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "font_weight", PROPERTY_HINT_RANGE, "100,999,25"), "set_font_weight", "get_font_weight"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "font_stretch", PROPERTY_HINT_RANGE, "50,200,25"), "set_font_stretch", "get_font_stretch"); ADD_PROPERTY(PropertyInfo(Variant::INT, "antialiasing", PROPERTY_HINT_ENUM, "None,Grayscale,LCD Subpixel", PROPERTY_USAGE_STORAGE), "set_antialiasing", "get_antialiasing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_mipmaps"), "set_generate_mipmaps", "get_generate_mipmaps"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_system_fallback"), "set_allow_system_fallback", "is_allow_system_fallback"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "force_autohinter"), "set_force_autohinter", "is_force_autohinter"); ADD_PROPERTY(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), "set_hinting", "get_hinting"); ADD_PROPERTY(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One Half of a Pixel,One Quarter of a Pixel"), "set_subpixel_positioning", "get_subpixel_positioning"); @@ -2899,13 +2958,14 @@ void SystemFont::_update_base_font() { face_indeces.clear(); ftr_weight = 0; + ftr_stretch = 0; ftr_italic = 0; for (const String &E : names) { if (E.is_empty()) { continue; } - String path = OS::get_singleton()->get_system_font_path(E, style & TextServer::FONT_BOLD, style & TextServer::FONT_ITALIC); + String path = OS::get_singleton()->get_system_font_path(E, weight, stretch, italic); if (path.is_empty()) { continue; } @@ -2917,9 +2977,22 @@ void SystemFont::_update_base_font() { } // If it's a font collection check all faces to match requested style. + int best_score = 0; for (int i = 0; i < file->get_face_count(); i++) { file->set_face_index(0, i); - if (((file->get_font_style() & TextServer::FONT_BOLD) == (style & TextServer::FONT_BOLD)) && ((file->get_font_style() & TextServer::FONT_ITALIC) == (style & TextServer::FONT_ITALIC))) { + BitField<TextServer::FontStyle> style = file->get_font_style(); + int font_weight = file->get_font_weight(); + int font_stretch = file->get_font_stretch(); + int score = (20 - Math::abs(font_weight - weight) / 50); + score += (20 - Math::abs(font_stretch - stretch) / 10); + if (bool(style & TextServer::FONT_ITALIC) == italic) { + score += 30; + } + if (score > best_score) { + face_indeces.clear(); + } + if (score >= best_score) { + best_score = score; face_indeces.push_back(i); } } @@ -2928,19 +3001,25 @@ void SystemFont::_update_base_font() { } file->set_face_index(0, face_indeces[0]); - // If it's a variable font, apply weight and italic coordinates to match requested style. - Dictionary ftr = file->get_supported_variation_list(); - if ((style & TextServer::FONT_BOLD) && ftr.has(TS->name_to_tag("weight"))) { - ftr_weight = 700; - } - if ((style & TextServer::FONT_ITALIC) && ftr.has(TS->name_to_tag("italic"))) { - ftr_italic = 1; + // If it's a variable font, apply weight, stretch and italic coordinates to match requested style. + if (best_score != 50) { + Dictionary ftr = file->get_supported_variation_list(); + if (ftr.has(TS->name_to_tag("width"))) { + ftr_stretch = stretch; + } + if (ftr.has(TS->name_to_tag("weight"))) { + ftr_weight = weight; + } + if (italic && ftr.has(TS->name_to_tag("italic"))) { + ftr_italic = 1; + } } // Apply font rendering settings. file->set_antialiasing(antialiasing); file->set_generate_mipmaps(mipmaps); file->set_force_autohinter(force_autohinter); + file->set_allow_system_fallback(allow_system_fallback); file->set_hinting(hinting); file->set_subpixel_positioning(subpixel_positioning); file->set_multichannel_signed_distance_field(msdf); @@ -2973,11 +3052,15 @@ void SystemFont::reset_state() { names.clear(); face_indeces.clear(); ftr_weight = 0; + ftr_stretch = 0; ftr_italic = 0; - style = 0; + italic = false; + weight = 400; + stretch = 100; antialiasing = TextServer::FONT_ANTIALIASING_GRAY; mipmaps = false; force_autohinter = false; + allow_system_fallback = true; hinting = TextServer::HINTING_LIGHT; subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_DISABLED; oversampling = 0.f; @@ -3069,6 +3152,20 @@ bool SystemFont::get_generate_mipmaps() const { return mipmaps; } +void SystemFont::set_allow_system_fallback(bool p_allow_system_fallback) { + if (allow_system_fallback != p_allow_system_fallback) { + allow_system_fallback = p_allow_system_fallback; + if (base_font.is_valid()) { + base_font->set_allow_system_fallback(allow_system_fallback); + } + emit_changed(); + } +} + +bool SystemFont::is_allow_system_fallback() const { + return allow_system_fallback; +} + void SystemFont::set_force_autohinter(bool p_force_autohinter) { if (force_autohinter != p_force_autohinter) { force_autohinter = p_force_autohinter; @@ -3150,15 +3247,37 @@ PackedStringArray SystemFont::get_font_names() const { return names; } -void SystemFont::set_font_style(BitField<TextServer::FontStyle> p_style) { - if (style != p_style) { - style = p_style; +void SystemFont::set_font_italic(bool p_italic) { + if (italic != p_italic) { + italic = p_italic; _update_base_font(); } } -BitField<TextServer::FontStyle> SystemFont::get_font_style() const { - return style; +bool SystemFont::get_font_italic() const { + return italic; +} + +void SystemFont::set_font_weight(int p_weight) { + if (weight != p_weight) { + weight = CLAMP(p_weight, 100, 999); + _update_base_font(); + } +} + +int SystemFont::get_font_weight() const { + return weight; +} + +void SystemFont::set_font_stretch(int p_stretch) { + if (stretch != p_stretch) { + stretch = CLAMP(p_stretch, 50, 200); + _update_base_font(); + } +} + +int SystemFont::get_font_stretch() const { + return stretch; } int SystemFont::get_spacing(TextServer::SpacingType p_spacing) const { @@ -3176,6 +3295,9 @@ RID SystemFont::find_variation(const Dictionary &p_variation_coordinates, int p_ if (ftr_weight > 0 && !var.has(TS->name_to_tag("weight"))) { var[TS->name_to_tag("weight")] = ftr_weight; } + if (ftr_stretch > 0 && !var.has(TS->name_to_tag("width"))) { + var[TS->name_to_tag("width")] = ftr_stretch; + } if (ftr_italic > 0 && !var.has(TS->name_to_tag("italic"))) { var[TS->name_to_tag("italic")] = ftr_italic; } @@ -3198,6 +3320,9 @@ RID SystemFont::_get_rid() const { if (ftr_weight > 0) { var[TS->name_to_tag("weight")] = ftr_weight; } + if (ftr_stretch > 0) { + var[TS->name_to_tag("width")] = ftr_stretch; + } if (ftr_italic > 0) { var[TS->name_to_tag("italic")] = ftr_italic; } diff --git a/scene/resources/font.h b/scene/resources/font.h index 5cf596b41d..e9f7507652 100644 --- a/scene/resources/font.h +++ b/scene/resources/font.h @@ -92,6 +92,8 @@ public: virtual String get_font_name() const; virtual String get_font_style_name() const; virtual BitField<TextServer::FontStyle> get_font_style() const; + virtual int get_font_weight() const; + virtual int get_font_stretch() const; virtual int get_spacing(TextServer::SpacingType p_spacing) const { return 0; }; virtual Dictionary get_opentype_features() const; @@ -148,6 +150,7 @@ class FontFile : public Font { int msdf_size = 48; int fixed_size = 0; bool force_autohinter = false; + bool allow_system_fallback = true; TextServer::Hinting hinting = TextServer::HINTING_LIGHT; TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; real_t oversampling = 0.f; @@ -191,6 +194,8 @@ public: virtual void set_font_name(const String &p_name); virtual void set_font_style_name(const String &p_name); virtual void set_font_style(BitField<TextServer::FontStyle> p_style); + virtual void set_font_weight(int p_weight); + virtual void set_font_stretch(int p_stretch); virtual void set_antialiasing(TextServer::FontAntialiasing p_antialiasing); virtual TextServer::FontAntialiasing get_antialiasing() const; @@ -210,6 +215,9 @@ public: virtual void set_fixed_size(int p_fixed_size); virtual int get_fixed_size() const; + virtual void set_allow_system_fallback(bool p_allow_system_fallback); + virtual bool is_allow_system_fallback() const; + virtual void set_force_autohinter(bool p_force_autohinter); virtual bool is_force_autohinter() const; @@ -389,18 +397,22 @@ class SystemFont : public Font { GDCLASS(SystemFont, Font); PackedStringArray names; - BitField<TextServer::FontStyle> style = 0; + bool italic = false; + int weight = 400; + int stretch = 100; mutable Ref<Font> theme_font; Ref<FontFile> base_font; Vector<int> face_indeces; int ftr_weight = 0; + int ftr_stretch = 0; int ftr_italic = 0; TextServer::FontAntialiasing antialiasing = TextServer::FONT_ANTIALIASING_GRAY; bool mipmaps = false; bool force_autohinter = false; + bool allow_system_fallback = true; TextServer::Hinting hinting = TextServer::HINTING_LIGHT; TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; real_t oversampling = 0.f; @@ -423,6 +435,9 @@ public: virtual void set_generate_mipmaps(bool p_generate_mipmaps); virtual bool get_generate_mipmaps() const; + virtual void set_allow_system_fallback(bool p_allow_system_fallback); + virtual bool is_allow_system_fallback() const; + virtual void set_force_autohinter(bool p_force_autohinter); virtual bool is_force_autohinter() const; @@ -441,8 +456,14 @@ public: virtual void set_font_names(const PackedStringArray &p_names); virtual PackedStringArray get_font_names() const; - virtual void set_font_style(BitField<TextServer::FontStyle> p_style); - virtual BitField<TextServer::FontStyle> get_font_style() const override; + virtual void set_font_italic(bool p_italic); + virtual bool get_font_italic() const; + + virtual void set_font_weight(int p_weight); + virtual int get_font_weight() const override; + + virtual void set_font_stretch(int p_stretch); + virtual int get_font_stretch() const override; virtual int get_spacing(TextServer::SpacingType p_spacing) const override; diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index e457b2d377..a16d2c2072 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -1252,15 +1252,16 @@ void BaseMaterial3D::_update_shader() { } if (distance_fade != DISTANCE_FADE_DISABLED) { + // Use the slightly more expensive circular fade (distance to the object) instead of linear + // (Z distance), so that the fade is always the same regardless of the camera angle. if ((distance_fade == DISTANCE_FADE_OBJECT_DITHER || distance_fade == DISTANCE_FADE_PIXEL_DITHER)) { if (!RenderingServer::get_singleton()->is_low_end()) { code += " {\n"; if (distance_fade == DISTANCE_FADE_OBJECT_DITHER) { - code += " float fade_distance = abs((VIEW_MATRIX * MODEL_MATRIX[3]).z);\n"; - + code += " float fade_distance = length((VIEW_MATRIX * MODEL_MATRIX[3]));\n"; } else { - code += " float fade_distance = -VERTEX.z;\n"; + code += " float fade_distance = length(VERTEX);\n"; } // Use interleaved gradient noise, which is fast but still looks good. code += " const vec3 magic = vec3(0.06711056f, 0.00583715f, 52.9829189f);"; @@ -1274,7 +1275,7 @@ void BaseMaterial3D::_update_shader() { } } else { - code += " ALPHA*=clamp(smoothstep(distance_fade_min,distance_fade_max,-VERTEX.z),0.0,1.0);\n"; + code += " ALPHA *= clamp(smoothstep(distance_fade_min, distance_fade_max, length(VERTEX)), 0.0, 1.0);\n"; } } diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 354373ef3c..36e4a8ea37 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -1356,7 +1356,7 @@ Error ResourceLoaderText::save_as_binary(const String &p_path) { wf->seek_end(); - Vector<uint8_t> data = FileAccess::get_file_as_array(temp_file); + Vector<uint8_t> data = FileAccess::get_file_as_bytes(temp_file); wf->store_buffer(data.ptr(), data.size()); { Ref<DirAccess> dar = DirAccess::open(temp_file.get_base_dir()); diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp index 3a671edeea..48ec084b02 100644 --- a/scene/resources/shader.cpp +++ b/scene/resources/shader.cpp @@ -221,7 +221,7 @@ Ref<Resource> ResourceFormatLoaderShader::load(const String &p_path, const Strin Ref<Shader> shader; shader.instantiate(); - Vector<uint8_t> buffer = FileAccess::get_file_as_array(p_path); + Vector<uint8_t> buffer = FileAccess::get_file_as_bytes(p_path); String str; str.parse_utf8((const char *)buffer.ptr(), buffer.size()); diff --git a/scene/resources/shader_include.cpp b/scene/resources/shader_include.cpp index fe628dd323..a680e66a50 100644 --- a/scene/resources/shader_include.cpp +++ b/scene/resources/shader_include.cpp @@ -81,7 +81,7 @@ Ref<Resource> ResourceFormatLoaderShaderInclude::load(const String &p_path, cons Ref<ShaderInclude> shader_inc; shader_inc.instantiate(); - Vector<uint8_t> buffer = FileAccess::get_file_as_array(p_path); + Vector<uint8_t> buffer = FileAccess::get_file_as_bytes(p_path); String str; str.parse_utf8((const char *)buffer.ptr(), buffer.size()); diff --git a/scene/resources/shape_2d.cpp b/scene/resources/shape_2d.cpp index 413670d23e..87c6c36ee9 100644 --- a/scene/resources/shape_2d.cpp +++ b/scene/resources/shape_2d.cpp @@ -124,7 +124,6 @@ Shape2D::Shape2D(const RID &p_rid) { } Shape2D::~Shape2D() { - if (PhysicsServer2D::get_singleton() != nullptr) { - PhysicsServer2D::get_singleton()->free(shape); - } + ERR_FAIL_NULL(PhysicsServer2D::get_singleton()); + PhysicsServer2D::get_singleton()->free(shape); } diff --git a/scene/resources/shape_3d.cpp b/scene/resources/shape_3d.cpp index 44f21d2a48..7992ba9fd4 100644 --- a/scene/resources/shape_3d.cpp +++ b/scene/resources/shape_3d.cpp @@ -128,7 +128,6 @@ Shape3D::Shape3D(RID p_shape) : shape(p_shape) {} Shape3D::~Shape3D() { - if (PhysicsServer3D::get_singleton() != nullptr) { - PhysicsServer3D::get_singleton()->free(shape); - } + ERR_FAIL_NULL(PhysicsServer3D::get_singleton()); + PhysicsServer3D::get_singleton()->free(shape); } diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index b30ca3e721..461dccfbdd 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -2658,6 +2658,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "node_position_world", "NODE_POSITION_WORLD" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "camera_position_world", "CAMERA_POSITION_WORLD" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "camera_direction_world", "CAMERA_DIRECTION_WORLD" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR_INT, "camera_visible_layers", "CAMERA_VISIBLE_LAYERS" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR_3D, "node_position_view", "NODE_POSITION_VIEW" }, // Node3D, Fragment @@ -2690,6 +2691,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "node_position_world", "NODE_POSITION_WORLD" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "camera_position_world", "CAMERA_POSITION_WORLD" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "camera_direction_world", "CAMERA_DIRECTION_WORLD" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR_INT, "camera_visible_layers", "CAMERA_VISIBLE_LAYERS" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "node_position_view", "NODE_POSITION_VIEW" }, // Node3D, Light diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 52d7f66203..523cc714f0 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -367,8 +367,7 @@ float DisplayServer::screen_get_scale(int p_screen) const { return 1.0f; }; -bool DisplayServer::screen_is_touchscreen(int p_screen) const { - //return false; +bool DisplayServer::is_touchscreen_available() const { return Input::get_singleton() && Input::get_singleton()->is_emulating_touch_from_mouse(); } @@ -618,7 +617,7 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("screen_get_usable_rect", "screen"), &DisplayServer::screen_get_usable_rect, DEFVAL(SCREEN_OF_MAIN_WINDOW)); ClassDB::bind_method(D_METHOD("screen_get_dpi", "screen"), &DisplayServer::screen_get_dpi, DEFVAL(SCREEN_OF_MAIN_WINDOW)); ClassDB::bind_method(D_METHOD("screen_get_scale", "screen"), &DisplayServer::screen_get_scale, DEFVAL(SCREEN_OF_MAIN_WINDOW)); - ClassDB::bind_method(D_METHOD("screen_is_touchscreen", "screen"), &DisplayServer::screen_is_touchscreen, DEFVAL(SCREEN_OF_MAIN_WINDOW)); + ClassDB::bind_method(D_METHOD("is_touchscreen_available"), &DisplayServer::is_touchscreen_available, DEFVAL(SCREEN_OF_MAIN_WINDOW)); ClassDB::bind_method(D_METHOD("screen_get_max_scale"), &DisplayServer::screen_get_max_scale); ClassDB::bind_method(D_METHOD("screen_get_refresh_rate", "screen"), &DisplayServer::screen_get_refresh_rate, DEFVAL(SCREEN_OF_MAIN_WINDOW)); @@ -643,6 +642,7 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("window_set_current_screen", "screen", "window_id"), &DisplayServer::window_set_current_screen, DEFVAL(MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("window_get_position", "window_id"), &DisplayServer::window_get_position, DEFVAL(MAIN_WINDOW_ID)); + ClassDB::bind_method(D_METHOD("window_get_position_with_decorations", "window_id"), &DisplayServer::window_get_position_with_decorations, DEFVAL(MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("window_set_position", "position", "window_id"), &DisplayServer::window_set_position, DEFVAL(MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("window_get_size", "window_id"), &DisplayServer::window_get_size, DEFVAL(MAIN_WINDOW_ID)); @@ -661,7 +661,7 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("window_get_min_size", "window_id"), &DisplayServer::window_get_min_size, DEFVAL(MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("window_set_min_size", "min_size", "window_id"), &DisplayServer::window_set_min_size, DEFVAL(MAIN_WINDOW_ID)); - ClassDB::bind_method(D_METHOD("window_get_real_size", "window_id"), &DisplayServer::window_get_real_size, DEFVAL(MAIN_WINDOW_ID)); + ClassDB::bind_method(D_METHOD("window_get_size_with_decorations", "window_id"), &DisplayServer::window_get_size_with_decorations, DEFVAL(MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("window_get_mode", "window_id"), &DisplayServer::window_get_mode, DEFVAL(MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("window_set_mode", "mode", "window_id"), &DisplayServer::window_set_mode, DEFVAL(MAIN_WINDOW_ID)); diff --git a/servers/display_server.h b/servers/display_server.h index a796377f88..4c36b0acb9 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -262,7 +262,7 @@ public: return scale; } virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const = 0; - virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + virtual bool is_touchscreen_available() const; // Keep the ScreenOrientation enum values in sync with the `display/window/handheld/orientation` // project setting hint. @@ -353,6 +353,7 @@ public: virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) = 0; virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const = 0; + virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const = 0; virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) = 0; virtual void window_set_transient(WindowID p_window, WindowID p_parent) = 0; @@ -366,7 +367,7 @@ public: virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) = 0; virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const = 0; - virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const = 0; // FIXME: Find clearer name for this. + virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const = 0; virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) = 0; virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const = 0; diff --git a/servers/display_server_headless.h b/servers/display_server_headless.h index e1a27b6137..94b5f3c1e9 100644 --- a/servers/display_server_headless.h +++ b/servers/display_server_headless.h @@ -90,6 +90,7 @@ public: void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override {} Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override { return Point2i(); } + Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override { return Point2i(); } void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override {} void window_set_transient(WindowID p_window, WindowID p_parent) override {} @@ -102,7 +103,7 @@ public: void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override {} Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override { return Size2i(); } - Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override { return Size2i(); } + Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override { return Size2i(); } void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override {} WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override { return WINDOW_MODE_MINIMIZED; } diff --git a/servers/physics_2d/godot_shape_2d.cpp b/servers/physics_2d/godot_shape_2d.cpp index da414ae233..6823cb32c1 100644 --- a/servers/physics_2d/godot_shape_2d.cpp +++ b/servers/physics_2d/godot_shape_2d.cpp @@ -369,8 +369,9 @@ void GodotCapsuleShape2D::get_supports(const Vector2 &p_normal, Vector2 *r_suppo Vector2 n = p_normal; real_t d = n.y; + real_t h = height * 0.5 - radius; // half-height of the rectangle part - if (Math::abs(d) < (1.0 - _SEGMENT_IS_VALID_SUPPORT_THRESHOLD)) { + if (h > 0 && Math::abs(d) < (1.0 - _SEGMENT_IS_VALID_SUPPORT_THRESHOLD)) { // make it flat n.y = 0.0; n.normalize(); @@ -378,13 +379,10 @@ void GodotCapsuleShape2D::get_supports(const Vector2 &p_normal, Vector2 *r_suppo r_amount = 2; r_supports[0] = n; - r_supports[0].y += height * 0.5 - radius; + r_supports[0].y += h; r_supports[1] = n; - r_supports[1].y -= height * 0.5 - radius; - + r_supports[1].y -= h; } else { - real_t h = height * 0.5 - radius; - n *= radius; n.y += (d > 0) ? h : -h; r_amount = 1; diff --git a/servers/physics_2d/godot_step_2d.cpp b/servers/physics_2d/godot_step_2d.cpp index 46718c8819..b7d79bc0e3 100644 --- a/servers/physics_2d/godot_step_2d.cpp +++ b/servers/physics_2d/godot_step_2d.cpp @@ -71,7 +71,7 @@ void GodotStep2D::_populate_island(GodotBody2D *p_body, LocalVector<GodotBody2D } } -void GodotStep2D::_setup_contraint(uint32_t p_constraint_index, void *p_userdata) { +void GodotStep2D::_setup_constraint(uint32_t p_constraint_index, void *p_userdata) { GodotConstraint2D *constraint = all_constraints[p_constraint_index]; constraint->setup(delta); } @@ -238,8 +238,8 @@ void GodotStep2D::step(GodotSpace2D *p_space, real_t p_delta) { /* SETUP CONSTRAINTS / PROCESS COLLISIONS */ - uint32_t total_contraint_count = all_constraints.size(); - WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &GodotStep2D::_setup_contraint, nullptr, total_contraint_count, -1, true, SNAME("Physics2DConstraintSetup")); + uint32_t total_constraint_count = all_constraints.size(); + WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &GodotStep2D::_setup_constraint, nullptr, total_constraint_count, -1, true, SNAME("Physics2DConstraintSetup")); WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); { //profile diff --git a/servers/physics_2d/godot_step_2d.h b/servers/physics_2d/godot_step_2d.h index 9f8fdd6ce3..4b3b6fc966 100644 --- a/servers/physics_2d/godot_step_2d.h +++ b/servers/physics_2d/godot_step_2d.h @@ -47,7 +47,7 @@ class GodotStep2D { LocalVector<GodotConstraint2D *> all_constraints; void _populate_island(GodotBody2D *p_body, LocalVector<GodotBody2D *> &p_body_island, LocalVector<GodotConstraint2D *> &p_constraint_island); - void _setup_contraint(uint32_t p_constraint_index, void *p_userdata = nullptr); + void _setup_constraint(uint32_t p_constraint_index, void *p_userdata = nullptr); void _pre_solve_island(LocalVector<GodotConstraint2D *> &p_constraint_island) const; void _solve_island(uint32_t p_island_index, void *p_userdata = nullptr) const; void _check_suspend(LocalVector<GodotBody2D *> &p_body_island) const; diff --git a/servers/physics_3d/godot_collision_solver_3d_sat.cpp b/servers/physics_3d/godot_collision_solver_3d_sat.cpp index 96253cb452..8c78957789 100644 --- a/servers/physics_3d/godot_collision_solver_3d_sat.cpp +++ b/servers/physics_3d/godot_collision_solver_3d_sat.cpp @@ -1908,7 +1908,7 @@ static void _collision_cylinder_cylinder(const GodotShape3D *p_a, const Transfor } // Cylinder B end caps. - if (!separator.test_axis(cylinder_A_axis.normalized())) { + if (!separator.test_axis(cylinder_B_axis.normalized())) { return; } diff --git a/servers/physics_3d/godot_shape_3d.cpp b/servers/physics_3d/godot_shape_3d.cpp index 1443cd166b..22553bd3d8 100644 --- a/servers/physics_3d/godot_shape_3d.cpp +++ b/servers/physics_3d/godot_shape_3d.cpp @@ -521,8 +521,9 @@ void GodotCapsuleShape3D::get_supports(const Vector3 &p_normal, int p_max, Vecto Vector3 n = p_normal; real_t d = n.y; + real_t h = height * 0.5 - radius; // half-height of the cylinder part - if (Math::abs(d) < edge_support_threshold) { + if (h > 0 && Math::abs(d) < edge_support_threshold) { // make it flat n.y = 0.0; n.normalize(); @@ -531,13 +532,10 @@ void GodotCapsuleShape3D::get_supports(const Vector3 &p_normal, int p_max, Vecto r_amount = 2; r_type = FEATURE_EDGE; r_supports[0] = n; - r_supports[0].y += height * 0.5 - radius; + r_supports[0].y += h; r_supports[1] = n; - r_supports[1].y -= height * 0.5 - radius; - + r_supports[1].y -= h; } else { - real_t h = height * 0.5 - radius; - n *= radius; n.y += (d > 0) ? h : -h; r_amount = 1; diff --git a/servers/physics_3d/godot_step_3d.cpp b/servers/physics_3d/godot_step_3d.cpp index bfedcd29c0..634523d0b9 100644 --- a/servers/physics_3d/godot_step_3d.cpp +++ b/servers/physics_3d/godot_step_3d.cpp @@ -111,7 +111,7 @@ void GodotStep3D::_populate_island_soft_body(GodotSoftBody3D *p_soft_body, Local } } -void GodotStep3D::_setup_contraint(uint32_t p_constraint_index, void *p_userdata) { +void GodotStep3D::_setup_constraint(uint32_t p_constraint_index, void *p_userdata) { GodotConstraint3D *constraint = all_constraints[p_constraint_index]; constraint->setup(delta); } @@ -342,8 +342,8 @@ void GodotStep3D::step(GodotSpace3D *p_space, real_t p_delta) { /* SETUP CONSTRAINTS / PROCESS COLLISIONS */ - uint32_t total_contraint_count = all_constraints.size(); - WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &GodotStep3D::_setup_contraint, nullptr, total_contraint_count, -1, true, SNAME("Physics3DConstraintSetup")); + uint32_t total_constraint_count = all_constraints.size(); + WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &GodotStep3D::_setup_constraint, nullptr, total_constraint_count, -1, true, SNAME("Physics3DConstraintSetup")); WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); { //profile diff --git a/servers/physics_3d/godot_step_3d.h b/servers/physics_3d/godot_step_3d.h index 189487757f..a0854a720c 100644 --- a/servers/physics_3d/godot_step_3d.h +++ b/servers/physics_3d/godot_step_3d.h @@ -48,7 +48,7 @@ class GodotStep3D { void _populate_island(GodotBody3D *p_body, LocalVector<GodotBody3D *> &p_body_island, LocalVector<GodotConstraint3D *> &p_constraint_island); void _populate_island_soft_body(GodotSoftBody3D *p_soft_body, LocalVector<GodotBody3D *> &p_body_island, LocalVector<GodotConstraint3D *> &p_constraint_island); - void _setup_contraint(uint32_t p_constraint_index, void *p_userdata = nullptr); + void _setup_constraint(uint32_t p_constraint_index, void *p_userdata = nullptr); void _pre_solve_island(LocalVector<GodotConstraint3D *> &p_constraint_island) const; void _solve_island(uint32_t p_island_index, void *p_userdata = nullptr); void _check_suspend(const LocalVector<GodotBody3D *> &p_body_island) const; diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp index 075a21b818..33f1f38bf5 100644 --- a/servers/rendering/renderer_canvas_cull.cpp +++ b/servers/rendering/renderer_canvas_cull.cpp @@ -1075,18 +1075,36 @@ void RendererCanvasCull::canvas_item_add_polyline(RID p_item, const Vector<Point void RendererCanvasCull::canvas_item_add_multiline(RID p_item, const Vector<Point2> &p_points, const Vector<Color> &p_colors, float p_width) { ERR_FAIL_COND(p_points.size() < 2); - Item *canvas_item = canvas_item_owner.get_or_null(p_item); - ERR_FAIL_COND(!canvas_item); - - Item::CommandPolygon *pline = canvas_item->alloc_command<Item::CommandPolygon>(); - ERR_FAIL_COND(!pline); - if (true || p_width <= 1) { -#define TODO make thick lines possible + // TODO: `canvas_item_add_line`(`multiline`, `polyline`) share logic, should factor out. + if (p_width <= 1) { + Item *canvas_item = canvas_item_owner.get_or_null(p_item); + ERR_FAIL_COND(!canvas_item); + Item::CommandPolygon *pline = canvas_item->alloc_command<Item::CommandPolygon>(); + ERR_FAIL_COND(!pline); pline->primitive = RS::PRIMITIVE_LINES; pline->polygon.create(Vector<int>(), p_points, p_colors); } else { + if (p_colors.size() == 1) { + Color color = p_colors[0]; + for (int i = 0; i < p_points.size() >> 1; i++) { + Vector2 from = p_points[i * 2 + 0]; + Vector2 to = p_points[i * 2 + 1]; + + canvas_item_add_line(p_item, from, to, color, p_width); + } + } else if (p_colors.size() == p_points.size() >> 1) { + for (int i = 0; i < p_points.size() >> 1; i++) { + Color color = p_colors[i]; + Vector2 from = p_points[i * 2 + 0]; + Vector2 to = p_points[i * 2 + 1]; + + canvas_item_add_line(p_item, from, to, color, p_width); + } + } else { + ERR_FAIL_MSG("Length of p_colors is invalid."); + } } } diff --git a/servers/rendering/renderer_rd/environment/sky.cpp b/servers/rendering/renderer_rd/environment/sky.cpp index 0acd48c22a..36d26ad783 100644 --- a/servers/rendering/renderer_rd/environment/sky.cpp +++ b/servers/rendering/renderer_rd/environment/sky.cpp @@ -1661,7 +1661,7 @@ void SkyRD::update_res_buffers(RID p_env, uint32_t p_view_count, const Projectio projections = &camera; } - sky_transform = p_transform.basis * sky_transform; + sky_transform = sky_transform * p_transform.basis; if (shader_data->uses_quarter_res) { PipelineCacheRD *pipeline = &shader_data->pipelines[view_count > 1 ? SKY_VERSION_QUARTER_RES_MULTIVIEW : SKY_VERSION_QUARTER_RES]; @@ -1754,7 +1754,7 @@ void SkyRD::draw(RD::DrawListID p_draw_list, RID p_env, RID p_fb, uint32_t p_vie projections = &camera; } - sky_transform = p_transform.basis * sky_transform; + sky_transform = sky_transform * p_transform.basis; PipelineCacheRD *pipeline = &shader_data->pipelines[view_count > 1 ? SKY_VERSION_BACKGROUND_MULTIVIEW : SKY_VERSION_BACKGROUND]; diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp index 6c8ce5265e..b5d4098e65 100644 --- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp @@ -709,6 +709,7 @@ void SceneShaderForwardClustered::init(const String p_defines) { actions.renames["NODE_POSITION_WORLD"] = "read_model_matrix[3].xyz"; actions.renames["CAMERA_POSITION_WORLD"] = "scene_data.inv_view_matrix[3].xyz"; actions.renames["CAMERA_DIRECTION_WORLD"] = "scene_data.view_matrix[3].xyz"; + actions.renames["CAMERA_VISIBLE_LAYERS"] = "scene_data.camera_visible_layers"; actions.renames["NODE_POSITION_VIEW"] = "(read_model_matrix * scene_data.view_matrix)[3].xyz"; actions.renames["VIEW_INDEX"] = "ViewIndex"; diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp index ee82fa7c7d..2d80cffdad 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp @@ -609,6 +609,7 @@ void SceneShaderForwardMobile::init(const String p_defines) { actions.renames["NODE_POSITION_WORLD"] = "read_model_matrix[3].xyz"; actions.renames["CAMERA_POSITION_WORLD"] = "scene_data.inv_view_matrix[3].xyz"; actions.renames["CAMERA_DIRECTION_WORLD"] = "scene_data.view_matrix[3].xyz"; + actions.renames["CAMERA_VISIBLE_LAYERS"] = "scene_data.camera_visible_layers"; actions.renames["NODE_POSITION_VIEW"] = "(read_model_matrix * scene_data.view_matrix)[3].xyz"; actions.renames["VIEW_INDEX"] = "ViewIndex"; diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index 75fe84f46b..1b5665855f 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -1078,6 +1078,7 @@ void RendererSceneRenderRD::render_scene(const Ref<RenderSceneBuffers> &p_render scene_data.cam_transform = p_camera_data->main_transform; scene_data.cam_projection = p_camera_data->main_projection; scene_data.cam_orthogonal = p_camera_data->is_orthogonal; + scene_data.camera_visible_layers = p_camera_data->visible_layers; scene_data.taa_jitter = p_camera_data->taa_jitter; scene_data.view_count = p_camera_data->view_count; @@ -1418,10 +1419,13 @@ void RendererSceneRenderRD::init() { cull_argument.set_page_pool(&cull_argument_pool); bool can_use_storage = _render_buffers_can_be_storage(); + bool can_use_vrs = is_vrs_supported(); bokeh_dof = memnew(RendererRD::BokehDOF(!can_use_storage)); copy_effects = memnew(RendererRD::CopyEffects(!can_use_storage)); tone_mapper = memnew(RendererRD::ToneMapper); - vrs = memnew(RendererRD::VRS); + if (can_use_vrs) { + vrs = memnew(RendererRD::VRS); + } if (can_use_storage) { fsr = memnew(RendererRD::FSR); } diff --git a/servers/rendering/renderer_rd/shaders/scene_data_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_data_inc.glsl index 048257e9ef..b57ee18521 100644 --- a/servers/rendering/renderer_rd/shaders/scene_data_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_data_inc.glsl @@ -63,7 +63,7 @@ struct SceneData { float IBL_exposure_normalization; bool pancake_shadows; - uint pad1; + uint camera_visible_layers; uint pad2; uint pad3; }; diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index 71f4f3ad11..503a25184e 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -445,7 +445,14 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) for (int i = 0; i < p_surface.bone_aabbs.size(); i++) { const AABB &bone = p_surface.bone_aabbs[i]; if (bone.has_volume()) { - mesh->bone_aabbs.write[i].merge_with(bone); + AABB &mesh_bone = mesh->bone_aabbs.write[i]; + if (mesh_bone != AABB()) { + // Already initialized, merge AABBs. + mesh_bone.merge_with(bone); + } else { + // Not yet initialized, copy the bone AABB. + mesh_bone = bone; + } } } mesh->aabb.merge_with(p_surface.aabb); diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp index 7dd790d1da..9b3d5a2d29 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.cpp @@ -88,6 +88,7 @@ void RenderSceneDataRD::update_ubo(RID p_uniform_buffer, RS::ViewportDebugDraw p RendererRD::MaterialStorage::store_soft_shadow_kernel(render_scene_render->directional_soft_shadow_kernel_get(), ubo.directional_soft_shadow_kernel); RendererRD::MaterialStorage::store_soft_shadow_kernel(render_scene_render->penumbra_shadow_kernel_get(), ubo.penumbra_shadow_kernel); RendererRD::MaterialStorage::store_soft_shadow_kernel(render_scene_render->soft_shadow_kernel_get(), ubo.soft_shadow_kernel); + ubo.camera_visible_layers = camera_visible_layers; ubo.viewport_size[0] = p_screen_size.x; ubo.viewport_size[1] = p_screen_size.y; diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h index 9c031acc1e..5ea4991e8e 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_data_rd.h @@ -45,6 +45,7 @@ public: Transform3D cam_transform; Projection cam_projection; Vector2 taa_jitter; + uint32_t camera_visible_layers; bool cam_orthogonal = false; // For stereo rendering @@ -142,7 +143,7 @@ private: float IBL_exposure_normalization; // Adjusts for baked exposure. uint32_t pancake_shadows; - uint32_t pad1; + uint32_t camera_visible_layers; uint32_t pad2; uint32_t pad3; }; diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp index 4b68d1f5c0..cfc25fc577 100644 --- a/servers/rendering/renderer_scene_cull.cpp +++ b/servers/rendering/renderer_scene_cull.cpp @@ -2512,7 +2512,7 @@ void RendererSceneCull::render_camera(const Ref<RenderSceneBuffers> &p_render_bu } break; } - camera_data.set_camera(transform, projection, is_orthogonal, vaspect, jitter); + camera_data.set_camera(transform, projection, is_orthogonal, vaspect, jitter, camera->visible_layers); } else { // Setup our camera for our XR interface. // We can support multiple views here each with their own camera @@ -2534,7 +2534,7 @@ void RendererSceneCull::render_camera(const Ref<RenderSceneBuffers> &p_render_bu } if (view_count == 1) { - camera_data.set_camera(transforms[0], projections[0], false, camera->vaspect, jitter); + camera_data.set_camera(transforms[0], projections[0], false, camera->vaspect, jitter, camera->visible_layers); } else if (view_count == 2) { camera_data.set_multiview_camera(view_count, transforms, projections, false, camera->vaspect); } else { diff --git a/servers/rendering/renderer_scene_render.cpp b/servers/rendering/renderer_scene_render.cpp index f085168df3..3c069f6e2e 100644 --- a/servers/rendering/renderer_scene_render.cpp +++ b/servers/rendering/renderer_scene_render.cpp @@ -33,7 +33,7 @@ ///////////////////////////////////////////////////////////////////////////// // CameraData -void RendererSceneRender::CameraData::set_camera(const Transform3D p_transform, const Projection p_projection, bool p_is_orthogonal, bool p_vaspect, const Vector2 &p_taa_jitter) { +void RendererSceneRender::CameraData::set_camera(const Transform3D p_transform, const Projection p_projection, bool p_is_orthogonal, bool p_vaspect, const Vector2 &p_taa_jitter, const uint32_t p_visible_layers) { view_count = 1; is_orthogonal = p_is_orthogonal; vaspect = p_vaspect; @@ -41,6 +41,7 @@ void RendererSceneRender::CameraData::set_camera(const Transform3D p_transform, main_transform = p_transform; main_projection = p_projection; + visible_layers = p_visible_layers; view_offset[0] = Transform3D(); view_projection[0] = p_projection; taa_jitter = p_taa_jitter; @@ -49,6 +50,7 @@ void RendererSceneRender::CameraData::set_camera(const Transform3D p_transform, void RendererSceneRender::CameraData::set_multiview_camera(uint32_t p_view_count, const Transform3D *p_transforms, const Projection *p_projections, bool p_is_orthogonal, bool p_vaspect) { ERR_FAIL_COND_MSG(p_view_count != 2, "Incorrect view count for stereoscopic view"); + visible_layers = 0xFFFFFFFF; view_count = p_view_count; is_orthogonal = p_is_orthogonal; vaspect = p_vaspect; diff --git a/servers/rendering/renderer_scene_render.h b/servers/rendering/renderer_scene_render.h index a8df897077..2c2d5a5a9e 100644 --- a/servers/rendering/renderer_scene_render.h +++ b/servers/rendering/renderer_scene_render.h @@ -268,6 +268,7 @@ public: // flags uint32_t view_count; bool is_orthogonal; + uint32_t visible_layers; bool vaspect; // Main/center projection @@ -278,7 +279,7 @@ public: Projection view_projection[RendererSceneRender::MAX_RENDER_VIEWS]; Vector2 taa_jitter; - void set_camera(const Transform3D p_transform, const Projection p_projection, bool p_is_orthogonal, bool p_vaspect, const Vector2 &p_taa_jitter = Vector2()); + void set_camera(const Transform3D p_transform, const Projection p_projection, bool p_is_orthogonal, bool p_vaspect, const Vector2 &p_taa_jitter = Vector2(), uint32_t p_visible_layers = 0xFFFFFFFF); void set_multiview_camera(uint32_t p_view_count, const Transform3D *p_transforms, const Projection *p_projections, bool p_is_orthogonal, bool p_vaspect); }; diff --git a/servers/rendering/shader_types.cpp b/servers/rendering/shader_types.cpp index 5e1da7c15d..86dd6a8b7e 100644 --- a/servers/rendering/shader_types.cpp +++ b/servers/rendering/shader_types.cpp @@ -100,6 +100,7 @@ ShaderTypes::ShaderTypes() { shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["NODE_POSITION_WORLD"] = ShaderLanguage::TYPE_VEC3; shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["CAMERA_POSITION_WORLD"] = ShaderLanguage::TYPE_VEC3; shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["CAMERA_DIRECTION_WORLD"] = ShaderLanguage::TYPE_VEC3; + shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["CAMERA_VISIBLE_LAYERS"] = ShaderLanguage::TYPE_UINT; shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["NODE_POSITION_VIEW"] = ShaderLanguage::TYPE_VEC3; shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["VIEW_INDEX"] = constt(ShaderLanguage::TYPE_INT); @@ -147,6 +148,7 @@ ShaderTypes::ShaderTypes() { shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["NODE_POSITION_WORLD"] = ShaderLanguage::TYPE_VEC3; shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["CAMERA_POSITION_WORLD"] = ShaderLanguage::TYPE_VEC3; shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["CAMERA_DIRECTION_WORLD"] = ShaderLanguage::TYPE_VEC3; + shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["CAMERA_VISIBLE_LAYERS"] = ShaderLanguage::TYPE_UINT; shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["NODE_POSITION_VIEW"] = ShaderLanguage::TYPE_VEC3; shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["VIEW_INDEX"] = constt(ShaderLanguage::TYPE_INT); diff --git a/servers/text/text_server_extension.cpp b/servers/text/text_server_extension.cpp index dd4cb5accb..64e8b1b5f7 100644 --- a/servers/text/text_server_extension.cpp +++ b/servers/text/text_server_extension.cpp @@ -69,6 +69,12 @@ void TextServerExtension::_bind_methods() { GDVIRTUAL_BIND(_font_set_style_name, "font_rid", "name_style"); GDVIRTUAL_BIND(_font_get_style_name, "font_rid"); + GDVIRTUAL_BIND(_font_set_weight, "font_rid", "weight"); + GDVIRTUAL_BIND(_font_get_weight, "font_rid"); + + GDVIRTUAL_BIND(_font_set_stretch, "font_rid", "stretch"); + GDVIRTUAL_BIND(_font_get_stretch, "font_rid"); + GDVIRTUAL_BIND(_font_set_antialiasing, "font_rid", "antialiasing"); GDVIRTUAL_BIND(_font_get_antialiasing, "font_rid"); @@ -87,6 +93,9 @@ void TextServerExtension::_bind_methods() { GDVIRTUAL_BIND(_font_set_fixed_size, "font_rid", "fixed_size"); GDVIRTUAL_BIND(_font_get_fixed_size, "font_rid"); + GDVIRTUAL_BIND(_font_set_allow_system_fallback, "font_rid", "allow_system_fallback"); + GDVIRTUAL_BIND(_font_is_allow_system_fallback, "font_rid"); + GDVIRTUAL_BIND(_font_set_force_autohinter, "font_rid", "force_autohinter"); GDVIRTUAL_BIND(_font_is_force_autohinter, "font_rid"); @@ -308,6 +317,8 @@ void TextServerExtension::_bind_methods() { GDVIRTUAL_BIND(_string_to_lower, "string", "language"); GDVIRTUAL_BIND(_parse_structured_text, "parser_type", "args", "text"); + + GDVIRTUAL_BIND(_cleanup); } bool TextServerExtension::has_feature(Feature p_feature) const { @@ -434,6 +445,26 @@ String TextServerExtension::font_get_style_name(const RID &p_font_rid) const { return ret; } +void TextServerExtension::font_set_weight(const RID &p_font_rid, int64_t p_weight) { + GDVIRTUAL_CALL(_font_set_weight, p_font_rid, p_weight); +} + +int64_t TextServerExtension::font_get_weight(const RID &p_font_rid) const { + int64_t ret = 400; + GDVIRTUAL_CALL(_font_get_weight, p_font_rid, ret); + return ret; +} + +void TextServerExtension::font_set_stretch(const RID &p_font_rid, int64_t p_stretch) { + GDVIRTUAL_CALL(_font_set_stretch, p_font_rid, p_stretch); +} + +int64_t TextServerExtension::font_get_stretch(const RID &p_font_rid) const { + int64_t ret = 100; + GDVIRTUAL_CALL(_font_get_stretch, p_font_rid, ret); + return ret; +} + void TextServerExtension::font_set_name(const RID &p_font_rid, const String &p_name) { GDVIRTUAL_CALL(_font_set_name, p_font_rid, p_name); } @@ -504,6 +535,16 @@ int64_t TextServerExtension::font_get_fixed_size(const RID &p_font_rid) const { return ret; } +void TextServerExtension::font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) { + GDVIRTUAL_CALL(_font_set_allow_system_fallback, p_font_rid, p_allow_system_fallback); +} + +bool TextServerExtension::font_is_allow_system_fallback(const RID &p_font_rid) const { + bool ret = false; + GDVIRTUAL_CALL(_font_is_allow_system_fallback, p_font_rid, ret); + return ret; +} + void TextServerExtension::font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) { GDVIRTUAL_CALL(_font_set_force_autohinter, p_font_rid, p_force_autohinter); } @@ -1360,6 +1401,10 @@ bool TextServerExtension::spoof_check(const String &p_string) const { return TextServer::spoof_check(p_string); } +void TextServerExtension::cleanup() { + GDVIRTUAL_CALL(_cleanup); +} + TextServerExtension::TextServerExtension() { //NOP } diff --git a/servers/text/text_server_extension.h b/servers/text/text_server_extension.h index 992b708045..46ed43e9c8 100644 --- a/servers/text/text_server_extension.h +++ b/servers/text/text_server_extension.h @@ -109,6 +109,16 @@ public: GDVIRTUAL2(_font_set_style_name, RID, const String &); GDVIRTUAL1RC(String, _font_get_style_name, RID); + virtual void font_set_weight(const RID &p_font_rid, int64_t p_weight) override; + virtual int64_t font_get_weight(const RID &p_font_rid) const override; + GDVIRTUAL2(_font_set_weight, RID, int); + GDVIRTUAL1RC(int64_t, _font_get_weight, RID); + + virtual void font_set_stretch(const RID &p_font_rid, int64_t p_stretch) override; + virtual int64_t font_get_stretch(const RID &p_font_rid) const override; + GDVIRTUAL2(_font_set_stretch, RID, int); + GDVIRTUAL1RC(int64_t, _font_get_stretch, RID); + virtual void font_set_antialiasing(const RID &p_font_rid, TextServer::FontAntialiasing p_antialiasing) override; virtual TextServer::FontAntialiasing font_get_antialiasing(const RID &p_font_rid) const override; GDVIRTUAL2(_font_set_antialiasing, RID, TextServer::FontAntialiasing); @@ -154,6 +164,11 @@ public: GDVIRTUAL2(_font_set_transform, RID, Transform2D); GDVIRTUAL1RC(Transform2D, _font_get_transform, RID); + virtual void font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) override; + virtual bool font_is_allow_system_fallback(const RID &p_font_rid) const override; + GDVIRTUAL2(_font_set_allow_system_fallback, RID, bool); + GDVIRTUAL1RC(bool, _font_is_allow_system_fallback, RID); + virtual void font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) override; virtual bool font_is_force_autohinter(const RID &p_font_rid) const override; GDVIRTUAL2(_font_set_force_autohinter, RID, bool); @@ -514,6 +529,9 @@ public: GDVIRTUAL2RC(int64_t, _is_confusable, const String &, const PackedStringArray &); GDVIRTUAL1RC(bool, _spoof_check, const String &); + virtual void cleanup() override; + GDVIRTUAL0(_cleanup); + TextServerExtension(); ~TextServerExtension(); }; diff --git a/servers/text_server.cpp b/servers/text_server.cpp index dfeb5cb70c..1b9cd28cfb 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -223,6 +223,12 @@ void TextServer::_bind_methods() { ClassDB::bind_method(D_METHOD("font_set_style_name", "font_rid", "name"), &TextServer::font_set_style_name); ClassDB::bind_method(D_METHOD("font_get_style_name", "font_rid"), &TextServer::font_get_style_name); + ClassDB::bind_method(D_METHOD("font_set_weight", "font_rid", "weight"), &TextServer::font_set_weight); + ClassDB::bind_method(D_METHOD("font_get_weight", "font_rid"), &TextServer::font_get_weight); + + ClassDB::bind_method(D_METHOD("font_set_stretch", "font_rid", "weight"), &TextServer::font_set_stretch); + ClassDB::bind_method(D_METHOD("font_get_stretch", "font_rid"), &TextServer::font_get_stretch); + ClassDB::bind_method(D_METHOD("font_set_antialiasing", "font_rid", "antialiasing"), &TextServer::font_set_antialiasing); ClassDB::bind_method(D_METHOD("font_get_antialiasing", "font_rid"), &TextServer::font_get_antialiasing); @@ -241,6 +247,9 @@ void TextServer::_bind_methods() { ClassDB::bind_method(D_METHOD("font_set_fixed_size", "font_rid", "fixed_size"), &TextServer::font_set_fixed_size); ClassDB::bind_method(D_METHOD("font_get_fixed_size", "font_rid"), &TextServer::font_get_fixed_size); + ClassDB::bind_method(D_METHOD("font_set_allow_system_fallback", "font_rid", "allow_system_fallback"), &TextServer::font_set_allow_system_fallback); + ClassDB::bind_method(D_METHOD("font_is_allow_system_fallback", "font_rid"), &TextServer::font_is_allow_system_fallback); + ClassDB::bind_method(D_METHOD("font_set_force_autohinter", "font_rid", "force_autohinter"), &TextServer::font_set_force_autohinter); ClassDB::bind_method(D_METHOD("font_is_force_autohinter", "font_rid"), &TextServer::font_is_force_autohinter); diff --git a/servers/text_server.h b/servers/text_server.h index a4e6080fd0..5da38a627a 100644 --- a/servers/text_server.h +++ b/servers/text_server.h @@ -250,6 +250,12 @@ public: virtual void font_set_style_name(const RID &p_font_rid, const String &p_name) = 0; virtual String font_get_style_name(const RID &p_font_rid) const = 0; + virtual void font_set_weight(const RID &p_font_rid, int64_t p_weight) = 0; + virtual int64_t font_get_weight(const RID &p_font_rid) const = 0; + + virtual void font_set_stretch(const RID &p_font_rid, int64_t p_stretch) = 0; + virtual int64_t font_get_stretch(const RID &p_font_rid) const = 0; + virtual void font_set_antialiasing(const RID &p_font_rid, FontAntialiasing p_antialiasing) = 0; virtual FontAntialiasing font_get_antialiasing(const RID &p_font_rid) const = 0; @@ -268,6 +274,9 @@ public: virtual void font_set_fixed_size(const RID &p_font_rid, int64_t p_fixed_size) = 0; virtual int64_t font_get_fixed_size(const RID &p_font_rid) const = 0; + virtual void font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) = 0; + virtual bool font_is_allow_system_fallback(const RID &p_font_rid) const = 0; + virtual void font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) = 0; virtual bool font_is_force_autohinter(const RID &p_font_rid) const = 0; @@ -498,6 +507,8 @@ public: TypedArray<Vector2i> parse_structured_text(StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const; + virtual void cleanup() {} + TextServer(); ~TextServer(); }; diff --git a/tests/core/os/test_os.h b/tests/core/os/test_os.h index c46da5e156..086f0b9b0c 100644 --- a/tests/core/os/test_os.h +++ b/tests/core/os/test_os.h @@ -97,14 +97,14 @@ TEST_CASE("[OS] Feature tags") { OS::get_singleton()->has_feature("editor"), "The binary has the \"editor\" feature tag."); CHECK_MESSAGE( - !OS::get_singleton()->has_feature("standalone"), - "The binary does not have the \"standalone\" feature tag."); + !OS::get_singleton()->has_feature("template"), + "The binary does not have the \"template\" feature tag."); CHECK_MESSAGE( - OS::get_singleton()->has_feature("debug"), - "The binary has the \"debug\" feature tag."); + !OS::get_singleton()->has_feature("template_debug"), + "The binary does not have the \"template_debug\" feature tag."); CHECK_MESSAGE( - !OS::get_singleton()->has_feature("release"), - "The binary does not have the \"release\" feature tag."); + !OS::get_singleton()->has_feature("template_release"), + "The binary does not have the \"template_release\" feature tag."); } TEST_CASE("[OS] Process ID") { diff --git a/tests/scene/test_curve.h b/tests/scene/test_curve.h index 36ec0c0a4d..4afd5b293f 100644 --- a/tests/scene/test_curve.h +++ b/tests/scene/test_curve.h @@ -31,6 +31,7 @@ #ifndef TEST_CURVE_H #define TEST_CURVE_H +#include "core/math/math_funcs.h" #include "scene/resources/curve.h" #include "tests/test_macros.h" @@ -229,7 +230,7 @@ TEST_CASE("[Curve2D] Linear sampling should return exact value") { for (int i = 0; i < len; i++) { Vector2 pos = curve->sample_baked(i); - CHECK_MESSAGE(pos.x == i, "sample_baked should return exact value"); + CHECK_MESSAGE(Math::is_equal_approx(pos.x, i), "sample_baked should return exact value"); } } @@ -245,7 +246,7 @@ TEST_CASE("[Curve3D] Linear sampling should return exact value") { for (int i = 0; i < len; i++) { Vector3 pos = curve->sample_baked(i); - CHECK_MESSAGE(pos.x == i, "sample_baked should return exact value"); + CHECK_MESSAGE(Math::is_equal_approx(pos.x, i), "sample_baked should return exact value"); } } diff --git a/tests/servers/test_text_server.h b/tests/servers/test_text_server.h index 9ebd0f34b4..297f7d2068 100644 --- a/tests/servers/test_text_server.h +++ b/tests/servers/test_text_server.h @@ -68,8 +68,10 @@ TEST_SUITE("[TextServer]") { RID font1 = ts->create_font(); ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size); + ts->font_set_allow_system_fallback(font1, false); RID font2 = ts->create_font(); ts->font_set_data_ptr(font2, _font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size); + ts->font_set_allow_system_fallback(font2, false); Array font; font.push_back(font1); |