diff options
29 files changed, 706 insertions, 81 deletions
diff --git a/core/math/basis.cpp b/core/math/basis.cpp index 6b0ecadc7f..9796ac59c2 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -96,6 +96,14 @@ bool Basis::is_orthogonal() const { return m.is_equal_approx(identity); } +bool Basis::is_conformal() const { + const Vector3 x = get_column(0); + const Vector3 y = get_column(1); + const Vector3 z = get_column(2); + const real_t x_len_sq = x.length_squared(); + return Math::is_equal_approx(x_len_sq, y.length_squared()) && Math::is_equal_approx(x_len_sq, z.length_squared()) && Math::is_zero_approx(x.dot(y)) && Math::is_zero_approx(x.dot(z)) && Math::is_zero_approx(y.dot(z)); +} + bool Basis::is_diagonal() const { return ( Math::is_zero_approx(rows[0][1]) && Math::is_zero_approx(rows[0][2]) && diff --git a/core/math/basis.h b/core/math/basis.h index 1a68bee686..adacd1c216 100644 --- a/core/math/basis.h +++ b/core/math/basis.h @@ -138,6 +138,7 @@ struct _NO_DISCARD_ Basis { _FORCE_INLINE_ Basis operator*(const real_t p_val) const; bool is_orthogonal() const; + bool is_conformal() const; bool is_diagonal() const; bool is_rotation() const; diff --git a/core/math/transform_2d.cpp b/core/math/transform_2d.cpp index a0187e00b1..bc4682fd90 100644 --- a/core/math/transform_2d.cpp +++ b/core/math/transform_2d.cpp @@ -164,6 +164,18 @@ Transform2D Transform2D::orthonormalized() const { return ortho; } +bool Transform2D::is_conformal() const { + // Non-flipped case. + if (Math::is_equal_approx(columns[0][0], columns[1][1]) && Math::is_equal_approx(columns[0][1], -columns[1][0])) { + return true; + } + // Flipped case. + if (Math::is_equal_approx(columns[0][0], -columns[1][1]) && Math::is_equal_approx(columns[0][1], columns[1][0])) { + return true; + } + return false; +} + bool Transform2D::is_equal_approx(const Transform2D &p_transform) const { return columns[0].is_equal_approx(p_transform.columns[0]) && columns[1].is_equal_approx(p_transform.columns[1]) && columns[2].is_equal_approx(p_transform.columns[2]); } diff --git a/core/math/transform_2d.h b/core/math/transform_2d.h index c511034669..dd1a33c5d5 100644 --- a/core/math/transform_2d.h +++ b/core/math/transform_2d.h @@ -96,6 +96,7 @@ struct _NO_DISCARD_ Transform2D { void orthonormalize(); Transform2D orthonormalized() const; + bool is_conformal() const; bool is_equal_approx(const Transform2D &p_transform) const; bool is_finite() const; diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index b757964ee0..c594f4a9b4 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -480,7 +480,6 @@ void ClassDB::get_method_list(const StringName &p_class, List<MethodInfo> *p_met } #ifdef DEBUG_METHODS_ENABLED - for (const MethodInfo &E : type->virtual_methods) { p_methods->push_back(E); } @@ -495,17 +494,74 @@ void ClassDB::get_method_list(const StringName &p_class, List<MethodInfo> *p_met p_methods->push_back(minfo); } - #else - for (KeyValue<StringName, MethodBind *> &E : type->method_map) { MethodBind *m = E.value; MethodInfo minfo = info_from_bind(m); p_methods->push_back(minfo); } +#endif + + if (p_no_inheritance) { + break; + } + + type = type->inherits_ptr; + } +} + +void ClassDB::get_method_list_with_compatibility(const StringName &p_class, List<Pair<MethodInfo, uint32_t>> *p_methods, bool p_no_inheritance, bool p_exclude_from_properties) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + + while (type) { + if (type->disabled) { + if (p_no_inheritance) { + break; + } + + type = type->inherits_ptr; + continue; + } + +#ifdef DEBUG_METHODS_ENABLED + for (const MethodInfo &E : type->virtual_methods) { + Pair<MethodInfo, uint32_t> pair(E, 0); + p_methods->push_back(pair); + } + + for (const StringName &E : type->method_order) { + if (p_exclude_from_properties && type->methods_in_properties.has(E)) { + continue; + } + + MethodBind *method = type->method_map.get(E); + MethodInfo minfo = info_from_bind(method); + + Pair<MethodInfo, uint32_t> pair(minfo, method->get_hash()); + p_methods->push_back(pair); + } +#else + for (KeyValue<StringName, MethodBind *> &E : type->method_map) { + MethodBind *method = E.value; + MethodInfo minfo = info_from_bind(method); + Pair<MethodInfo, uint32_t> pair(minfo, method->get_hash()); + p_methods->push_back(pair); + } #endif + for (const KeyValue<StringName, LocalVector<MethodBind *, unsigned int, false, false>> &E : type->method_map_compatibility) { + LocalVector<MethodBind *> compat = E.value; + for (MethodBind *method : compat) { + MethodInfo minfo = info_from_bind(method); + + Pair<MethodInfo, uint32_t> pair(minfo, method->get_hash()); + p_methods->push_back(pair); + } + } + if (p_no_inheritance) { break; } diff --git a/core/object/class_db.h b/core/object/class_db.h index cd2048d79d..5c2c59d508 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -399,6 +399,7 @@ public: static void set_method_flags(const StringName &p_class, const StringName &p_method, int p_flags); static void get_method_list(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance = false, bool p_exclude_from_properties = false); + static void get_method_list_with_compatibility(const StringName &p_class, List<Pair<MethodInfo, uint32_t>> *p_methods_with_hash, bool p_no_inheritance = false, bool p_exclude_from_properties = false); static bool get_method_info(const StringName &p_class, const StringName &p_method, MethodInfo *r_info, bool p_no_inheritance = false, bool p_exclude_from_properties = false); static MethodBind *get_method(const StringName &p_class, const StringName &p_name); static MethodBind *get_method_with_compatibility(const StringName &p_class, const StringName &p_name, uint64_t p_hash, bool *r_method_exists = nullptr, bool *r_is_deprecated = nullptr); diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 772a540d22..b21e23b3ec 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -2076,6 +2076,7 @@ static void _register_variant_builtin_methods() { bind_method(Transform2D, basis_xform, sarray("v"), varray()); bind_method(Transform2D, basis_xform_inv, sarray("v"), varray()); bind_method(Transform2D, interpolate_with, sarray("xform", "weight"), varray()); + bind_method(Transform2D, is_conformal, sarray(), varray()); bind_method(Transform2D, is_equal_approx, sarray("xform"), varray()); bind_method(Transform2D, is_finite, sarray(), varray()); // Do not bind functions like set_rotation, set_scale, set_skew, etc because this type is immutable and can't be modified. @@ -2095,6 +2096,7 @@ static void _register_variant_builtin_methods() { bind_method(Basis, tdoty, sarray("with"), varray()); bind_method(Basis, tdotz, sarray("with"), varray()); bind_method(Basis, slerp, sarray("to", "weight"), varray()); + bind_method(Basis, is_conformal, sarray(), varray()); bind_method(Basis, is_equal_approx, sarray("b"), varray()); bind_method(Basis, is_finite, sarray(), varray()); bind_method(Basis, get_rotation_quaternion, sarray(), varray()); diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml index 2034f4a8ff..972a8eb114 100644 --- a/doc/classes/Basis.xml +++ b/doc/classes/Basis.xml @@ -106,6 +106,12 @@ Returns the inverse of the matrix. </description> </method> + <method name="is_conformal" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if the basis is conformal, meaning it preserves angles and distance ratios, and may only be composed of rotation and uniform scale. Returns [code]false[/code] if the basis has non-uniform scale or shear/skew. This can be used to validate if the basis is non-distorted, which is important for physics and other use cases. + </description> + </method> <method name="is_equal_approx" qualifiers="const"> <return type="bool" /> <param index="0" name="b" type="Basis" /> diff --git a/doc/classes/CharFXTransform.xml b/doc/classes/CharFXTransform.xml index ad41eceff4..ce74227298 100644 --- a/doc/classes/CharFXTransform.xml +++ b/doc/classes/CharFXTransform.xml @@ -49,6 +49,9 @@ <member name="relative_index" type="int" setter="set_relative_index" getter="get_relative_index" default="0"> The character offset of the glyph, relative to the current [RichTextEffect] custom block. Setting this property won't affect drawing. </member> + <member name="transform" type="Transform2D" setter="set_transform" getter="get_transform" default="Transform2D(1, 0, 0, 1, 0, 0)"> + The current transform of the current glyph. It can be overridden (for example, by driving the position and rotation from a curve). You can also alter the existing value to apply transforms on top of other effects. + </member> <member name="visible" type="bool" setter="set_visibility" getter="is_visible" default="true"> If [code]true[/code], the character will be drawn. If [code]false[/code], the character will be hidden. Characters around hidden characters will reflow to take the space of hidden characters. If this is not desired, set their [member color] to [code]Color(1, 1, 1, 0)[/code] instead. </member> diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index c41072b20c..1e5b09516f 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -24,10 +24,17 @@ <param index="3" name="color" type="Color" default="Color(1, 1, 1, 1)" /> <param index="4" name="inline_align" type="int" enum="InlineAlignment" default="5" /> <param index="5" name="region" type="Rect2" default="Rect2(0, 0, 0, 0)" /> + <param index="6" name="key" type="Variant" default="null" /> + <param index="7" name="pad" type="bool" default="false" /> + <param index="8" name="tooltip" type="String" default="""" /> + <param index="9" name="size_in_percent" type="bool" default="false" /> <description> Adds an image's opening and closing tags to the tag stack, optionally providing a [param width] and [param height] to resize the image, a [param color] to tint the image and a [param region] to only use parts of the image. If [param width] or [param height] is set to 0, the image size will be adjusted in order to keep the original aspect ratio. If [param width] and [param height] are not set, but [param region] is, the region's rect will be used. + [param key] is an optional identifier, that can be used to modify the image via [method update_image]. + If [param pad] is set, and the image is smaller than the size specified by [param width] and [param height], the image padding is added to match the size instead of upscaling. + If [param size_in_percent] is set, [param width] and [param height] values are percentages of the control width instead of pixels. </description> </method> <method name="add_text"> @@ -539,6 +546,23 @@ If [param expand] is [code]false[/code], the column will not contribute to the total ratio. </description> </method> + <method name="update_image"> + <return type="void" /> + <param index="0" name="key" type="Variant" /> + <param index="1" name="mask" type="int" enum="RichTextLabel.ImageUpdateMask" is_bitfield="true" /> + <param index="2" name="image" type="Texture2D" /> + <param index="3" name="width" type="int" default="0" /> + <param index="4" name="height" type="int" default="0" /> + <param index="5" name="color" type="Color" default="Color(1, 1, 1, 1)" /> + <param index="6" name="inline_align" type="int" enum="InlineAlignment" default="5" /> + <param index="7" name="region" type="Rect2" default="Rect2(0, 0, 0, 0)" /> + <param index="8" name="pad" type="bool" default="false" /> + <param index="9" name="tooltip" type="String" default="""" /> + <param index="10" name="size_in_percent" type="bool" default="false" /> + <description> + Updates the existing images with the key [param key]. Only properties specified by [param mask] bits are updated. See [method add_image]. + </description> + </method> </methods> <members> <member name="autowrap_mode" type="int" setter="set_autowrap_mode" getter="get_autowrap_mode" enum="TextServer.AutowrapMode" default="3"> @@ -667,6 +691,30 @@ <constant name="MENU_MAX" value="2" enum="MenuItems"> Represents the size of the [enum MenuItems] enum. </constant> + <constant name="UPDATE_TEXTURE" value="1" enum="ImageUpdateMask" is_bitfield="true"> + If this bit is set, [method update_image] changes image texture. + </constant> + <constant name="UPDATE_SIZE" value="2" enum="ImageUpdateMask" is_bitfield="true"> + If this bit is set, [method update_image] changes image size. + </constant> + <constant name="UPDATE_COLOR" value="4" enum="ImageUpdateMask" is_bitfield="true"> + If this bit is set, [method update_image] changes image color. + </constant> + <constant name="UPDATE_ALIGNMENT" value="8" enum="ImageUpdateMask" is_bitfield="true"> + If this bit is set, [method update_image] changes image inline alignment. + </constant> + <constant name="UPDATE_REGION" value="16" enum="ImageUpdateMask" is_bitfield="true"> + If this bit is set, [method update_image] changes image texture region. + </constant> + <constant name="UPDATE_PAD" value="32" enum="ImageUpdateMask" is_bitfield="true"> + If this bit is set, [method update_image] changes image padding. + </constant> + <constant name="UPDATE_TOOLTIP" value="64" enum="ImageUpdateMask" is_bitfield="true"> + If this bit is set, [method update_image] changes image tooltip. + </constant> + <constant name="UPDATE_WIDTH_IN_PERCENT" value="128" enum="ImageUpdateMask" is_bitfield="true"> + If this bit is set, [method update_image] changes image width from/to percents. + </constant> </constants> <theme_items> <theme_item name="default_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> diff --git a/doc/classes/Transform2D.xml b/doc/classes/Transform2D.xml index cd79987ce9..6295412692 100644 --- a/doc/classes/Transform2D.xml +++ b/doc/classes/Transform2D.xml @@ -123,6 +123,12 @@ Returns the inverse of the transform, under the assumption that the transformation is composed of rotation and translation (no scaling, use [method affine_inverse] for transforms with scaling). </description> </method> + <method name="is_conformal" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if the transform's basis is conformal, meaning it preserves angles and distance ratios, and may only be composed of rotation and uniform scale. Returns [code]false[/code] if the transform's basis has non-uniform scale or shear/skew. This can be used to validate if the transform is non-distorted, which is important for physics and other use cases. + </description> + </method> <method name="is_equal_approx" qualifiers="const"> <return type="bool" /> <param index="0" name="xform" type="Transform2D" /> diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index e35b305837..b3bcb9f014 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -2269,20 +2269,46 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control pos = brk_end + 1; tag_stack.push_front("url"); - } else if (tag == "img") { + } else if (tag.begins_with("img")) { + int width = 0; + int height = 0; + bool size_in_percent = false; + if (tag.length() > 4) { + Vector<String> subtags = tag.substr(4, tag.length()).split(" "); + HashMap<String, String> bbcode_options; + for (int i = 0; i < subtags.size(); i++) { + const String &expr = subtags[i]; + int value_pos = expr.find("="); + if (value_pos > -1) { + bbcode_options[expr.substr(0, value_pos)] = expr.substr(value_pos + 1).unquote(); + } + } + HashMap<String, String>::Iterator width_option = bbcode_options.find("width"); + if (width_option) { + width = width_option->value.to_int(); + if (width_option->value.ends_with("%")) { + size_in_percent = true; + } + } + + HashMap<String, String>::Iterator height_option = bbcode_options.find("height"); + if (height_option) { + height = height_option->value.to_int(); + if (height_option->value.ends_with("%")) { + size_in_percent = true; + } + } + } int end = bbcode.find("[", brk_end); if (end == -1) { end = bbcode.length(); } String image = bbcode.substr(brk_end + 1, end - brk_end - 1); - Ref<Texture2D> texture = ResourceLoader::load(base_path.path_join(image), "Texture2D"); - if (texture.is_valid()) { - p_rt->add_image(texture); - } + p_rt->add_image(ResourceLoader::load(base_path.path_join(image), "Texture2D"), width, height, Color(1, 1, 1), INLINE_ALIGNMENT_CENTER, Rect2(), Variant(), false, String(), size_in_percent); pos = end; - tag_stack.push_front(tag); + tag_stack.push_front("img"); } else if (tag.begins_with("color=")) { String col = tag.substr(6, tag.length()); Color color = Color::from_string(col, Color()); diff --git a/methods.py b/methods.py index e18b19b991..d68316f0f0 100644 --- a/methods.py +++ b/methods.py @@ -1021,7 +1021,7 @@ def get_compiler_version(env): # Not using -dumpversion as some GCC distros only return major, and # Clang used to return hardcoded 4.2.1: # https://reviews.llvm.org/D56803 try: - version = subprocess.check_output([env.subst(env["CXX"]), "--version"], shell=True).strip().decode("utf-8") + version = subprocess.check_output([env.subst(env["CXX"]), "--version"]).strip().decode("utf-8") except (subprocess.CalledProcessError, OSError): print("Couldn't parse CXX environment variable to infer compiler version.") return ret diff --git a/misc/extension_api_validation/4.1-stable.expected b/misc/extension_api_validation/4.1-stable.expected index 13f09436df..376dfb145c 100644 --- a/misc/extension_api_validation/4.1-stable.expected +++ b/misc/extension_api_validation/4.1-stable.expected @@ -169,6 +169,8 @@ Validate extension JSON: API was removed: classes/GraphNode/signals/raise_reques Validate extension JSON: API was removed: classes/GraphNode/signals/resize_request Refactor GraphNode (splitup in GraphElement and GraphNode) + + GH-81070 -------- Validate extension JSON: API was removed: classes/TileMap/methods/get_quadrant_size @@ -190,4 +192,9 @@ GH-79965 -------- Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/PopupMenu/methods/clear': arguments + +GH-80410 +-------- +Validate extension JSON: Error: Field 'classes/RichTextLabel/methods/add_image/arguments': size changed value in new API, from 6 to 10. + Added optional argument. Compatibility method registered. diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 839846f963..ccfcf2a87c 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -94,6 +94,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) { #define ICALL_PREFIX "godot_icall_" #define ICALL_CLASSDB_GET_METHOD "ClassDB_get_method" +#define ICALL_CLASSDB_GET_METHOD_WITH_COMPATIBILITY "ClassDB_get_method_with_compatibility" #define ICALL_CLASSDB_GET_CONSTRUCTOR "ClassDB_get_constructor" #define C_LOCAL_RET "ret" @@ -1398,6 +1399,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append("namespace " BINDINGS_NAMESPACE ";\n\n"); output.append("using System;\n"); // IntPtr + output.append("using System.ComponentModel;\n"); // EditorBrowsable output.append("using System.Diagnostics;\n"); // DebuggerBrowsable output.append("using Godot.NativeInterop;\n"); @@ -1865,7 +1867,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str } output << "\n" << INDENT1 "{\n"; + HashMap<String, StringName> method_names; for (const MethodInterface &imethod : itype.methods) { + if (method_names.has(imethod.proxy_name)) { + ERR_FAIL_COND_V_MSG(method_names[imethod.proxy_name] != imethod.cname, ERR_BUG, "Method name '" + imethod.proxy_name + "' already exists with a different value."); + continue; + } + method_names[imethod.proxy_name] = imethod.cname; output << INDENT2 "/// <summary>\n" << INDENT2 "/// Cached name for the '" << imethod.cname << "' method.\n" << INDENT2 "/// </summary>\n" @@ -2114,7 +2122,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf arguments_sig += iarg.name; - if (iarg.default_argument.size()) { + if (!p_imethod.is_compat && iarg.default_argument.size()) { if (iarg.def_param_mode != ArgumentInterface::CONSTANT) { arguments_sig += " = null"; } else { @@ -2202,8 +2210,8 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output << "GodotObject."; } - p_output << ICALL_CLASSDB_GET_METHOD "(" BINDINGS_NATIVE_NAME_FIELD ", MethodName." - << p_imethod.proxy_name + p_output << ICALL_CLASSDB_GET_METHOD_WITH_COMPATIBILITY "(" BINDINGS_NATIVE_NAME_FIELD ", MethodName." + << p_imethod.proxy_name << ", " << itos(p_imethod.hash) << "ul" << ");\n"; } @@ -2242,6 +2250,10 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append("\")]"); } + if (p_imethod.is_compat) { + p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]"); + } + p_output.append(MEMBER_BEGIN); p_output.append(p_imethod.is_internal ? "internal " : "public "); @@ -2958,6 +2970,12 @@ bool method_has_ptr_parameter(MethodInfo p_method_info) { return false; } +struct SortMethodWithHashes { + _FORCE_INLINE_ bool operator()(const Pair<MethodInfo, uint32_t> &p_a, const Pair<MethodInfo, uint32_t> &p_b) const { + return p_a.first < p_b.first; + } +}; + bool BindingsGenerator::_populate_object_type_interfaces() { obj_types.clear(); @@ -3085,11 +3103,15 @@ bool BindingsGenerator::_populate_object_type_interfaces() { List<MethodInfo> virtual_method_list; ClassDB::get_virtual_methods(type_cname, &virtual_method_list, true); - List<MethodInfo> method_list; - ClassDB::get_method_list(type_cname, &method_list, true); - method_list.sort(); + List<Pair<MethodInfo, uint32_t>> method_list_with_hashes; + ClassDB::get_method_list_with_compatibility(type_cname, &method_list_with_hashes, true); + method_list_with_hashes.sort_custom_inplace<SortMethodWithHashes>(); + + List<MethodInterface> compat_methods; + for (const Pair<MethodInfo, uint32_t> &E : method_list_with_hashes) { + const MethodInfo &method_info = E.first; + const uint32_t hash = E.second; - for (const MethodInfo &method_info : method_list) { int argc = method_info.arguments.size(); if (method_info.name.is_empty()) { @@ -3111,6 +3133,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { MethodInterface imethod; imethod.name = method_info.name; imethod.cname = cname; + imethod.hash = hash; if (method_info.flags & METHOD_FLAG_STATIC) { imethod.is_static = true; @@ -3123,7 +3146,17 @@ bool BindingsGenerator::_populate_object_type_interfaces() { PropertyInfo return_info = method_info.return_val; - MethodBind *m = imethod.is_virtual ? nullptr : ClassDB::get_method(type_cname, method_info.name); + MethodBind *m = nullptr; + + if (!imethod.is_virtual) { + bool method_exists = false; + m = ClassDB::get_method_with_compatibility(type_cname, method_info.name, hash, &method_exists, &imethod.is_compat); + + if (unlikely(!method_exists)) { + ERR_FAIL_COND_V_MSG(!virtual_method_list.find(method_info), false, + "Missing MethodBind for non-virtual method: '" + itype.name + "." + imethod.name + "'."); + } + } imethod.is_vararg = m && m->is_vararg(); @@ -3244,6 +3277,14 @@ bool BindingsGenerator::_populate_object_type_interfaces() { ERR_FAIL_COND_V_MSG(itype.find_property_by_name(imethod.cname), false, "Method name conflicts with property: '" + itype.name + "." + imethod.name + "'."); + // Compat methods aren't added to the type yet, they need to be checked for conflicts + // after all the non-compat methods have been added. The compat methods are added in + // reverse so the most recently added ones take precedence over older compat methods. + if (imethod.is_compat) { + compat_methods.push_front(imethod); + continue; + } + // Methods starting with an underscore are ignored unless they're used as a property setter or getter if (!imethod.is_virtual && imethod.name[0] == '_') { for (const PropertyInterface &iprop : itype.properties) { @@ -3258,6 +3299,15 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } } + // Add compat methods that don't conflict with other methods in the type. + for (const MethodInterface &imethod : compat_methods) { + if (_method_has_conflicting_signature(imethod, itype)) { + WARN_PRINT("Method '" + imethod.name + "' conflicts with an already existing method in type '" + itype.name + "' and has been ignored."); + continue; + } + itype.methods.push_back(imethod); + } + // Populate signals const HashMap<StringName, MethodInfo> &signal_map = class_info->signal_map; @@ -4039,6 +4089,50 @@ void BindingsGenerator::_populate_global_constants() { } } +bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod, const TypeInterface &p_itype) { + // Compare p_imethod with all the methods already registered in p_itype. + for (const MethodInterface &method : p_itype.methods) { + if (method.proxy_name == p_imethod.proxy_name) { + if (_method_has_conflicting_signature(p_imethod, method)) { + return true; + } + } + } + + return false; +} + +bool BindingsGenerator::_method_has_conflicting_signature(const MethodInterface &p_imethod_left, const MethodInterface &p_imethod_right) { + // Check if a method already exists in p_itype with a method signature that would conflict with p_imethod. + // The return type is ignored because only changing the return type is not enough to avoid conflicts. + // The const keyword is also ignored since it doesn't generate different C# code. + + if (p_imethod_left.arguments.size() != p_imethod_right.arguments.size()) { + // Different argument count, so no conflict. + return false; + } + + for (int i = 0; i < p_imethod_left.arguments.size(); i++) { + const ArgumentInterface &iarg_left = p_imethod_left.arguments[i]; + const ArgumentInterface &iarg_right = p_imethod_right.arguments[i]; + + if (iarg_left.type.cname != iarg_right.type.cname) { + // Different types for arguments in the same position, so no conflict. + return false; + } + + if (iarg_left.def_param_mode != iarg_right.def_param_mode) { + // If the argument is a value type and nullable, it will be 'Nullable<T>' instead of 'T' + // and will not create a conflict. + if (iarg_left.def_param_mode == ArgumentInterface::NULLABLE_VAL || iarg_right.def_param_mode == ArgumentInterface::NULLABLE_VAL) { + return false; + } + } + } + + return true; +} + void BindingsGenerator::_initialize_blacklisted_methods() { blacklisted_methods["Object"].push_back("to_string"); // there is already ToString blacklisted_methods["Object"].push_back("_to_string"); // override ToString instead diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 6118576bb6..aa4e5ea093 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -134,6 +134,11 @@ class BindingsGenerator { String proxy_name; /** + * Hash of the ClassDB method + */ + uint64_t hash = 0; + + /** * [TypeInterface::name] of the return type */ TypeReference return_type; @@ -168,6 +173,12 @@ class BindingsGenerator { */ bool is_internal = false; + /** + * Determines if the method is a compatibility method added to avoid breaking binary compatibility. + * These methods will be generated but hidden and are considered deprecated. + */ + bool is_compat = false; + List<ArgumentInterface> arguments; const DocData::MethodDoc *method_doc = nullptr; @@ -787,6 +798,9 @@ class BindingsGenerator { void _populate_global_constants(); + bool _method_has_conflicting_signature(const MethodInterface &p_imethod, const TypeInterface &p_itype); + bool _method_has_conflicting_signature(const MethodInterface &p_imethod_left, const MethodInterface &p_imethod_right); + Error _generate_cs_type(const TypeInterface &itype, const String &p_output_file); Error _generate_cs_property(const TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs b/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs index 48b47b166a..dd0affdb75 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Compat.cs @@ -83,6 +83,13 @@ partial class RenderingDevice partial class RichTextLabel { + /// <inheritdoc cref="AddImage(Texture2D, int, int, Nullable{Color}, InlineAlignment, Nullable{Rect2}, Variant, bool, string, bool)"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public void AddImage(Texture2D image, int width, int height, Nullable<Color> color, InlineAlignment inlineAlign, Nullable<Rect2> region) + { + AddImage(image, width, height, color, inlineAlign, region, key: default, pad: false, tooltip: "", sizeInPercent: false); + } + /// <inheritdoc cref="PushList(int, ListType, bool, string)"/> [EditorBrowsable(EditorBrowsableState.Never)] public void PushList(int level, ListType type, bool capitalize) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs index c6337e56ef..43598ca84d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs @@ -247,6 +247,18 @@ namespace Godot return methodBind; } + internal static IntPtr ClassDB_get_method_with_compatibility(StringName type, StringName method, ulong hash) + { + var typeSelf = (godot_string_name)type.NativeValue; + var methodSelf = (godot_string_name)method.NativeValue; + IntPtr methodBind = NativeFuncs.godotsharp_method_bind_get_method_with_compatibility(typeSelf, methodSelf, hash); + + if (methodBind == IntPtr.Zero) + throw new NativeMethodBindNotFoundException(type + "." + method); + + return methodBind; + } + internal static unsafe delegate* unmanaged<IntPtr> ClassDB_get_constructor(StringName type) { // for some reason the '??' operator doesn't support 'delegate*' diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index d42ee15657..d181bf2c0f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -42,6 +42,9 @@ namespace Godot.NativeInterop public static partial IntPtr godotsharp_method_bind_get_method(in godot_string_name p_classname, in godot_string_name p_methodname); + public static partial IntPtr godotsharp_method_bind_get_method_with_compatibility( + in godot_string_name p_classname, in godot_string_name p_methodname, ulong p_hash); + public static partial delegate* unmanaged<IntPtr> godotsharp_get_class_constructor( in godot_string_name p_classname); diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 24a9d4030a..3518507f8c 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -68,6 +68,10 @@ MethodBind *godotsharp_method_bind_get_method(const StringName *p_classname, con return ClassDB::get_method(*p_classname, *p_methodname); } +MethodBind *godotsharp_method_bind_get_method_with_compatibility(const StringName *p_classname, const StringName *p_methodname, uint64_t p_hash) { + return ClassDB::get_method_with_compatibility(*p_classname, *p_methodname, p_hash); +} + godotsharp_class_creation_func godotsharp_get_class_constructor(const StringName *p_classname) { ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(*p_classname); if (class_info) { @@ -1416,6 +1420,7 @@ void godotsharp_object_to_string(Object *p_ptr, godot_string *r_str) { static const void *unmanaged_callbacks[]{ (void *)godotsharp_dotnet_module_is_initialized, (void *)godotsharp_method_bind_get_method, + (void *)godotsharp_method_bind_get_method_with_compatibility, (void *)godotsharp_get_class_constructor, (void *)godotsharp_engine_get_singleton, (void *)godotsharp_stack_info_vector_resize, diff --git a/scene/gui/rich_text_effect.cpp b/scene/gui/rich_text_effect.cpp index 6734f34579..e968321777 100644 --- a/scene/gui/rich_text_effect.cpp +++ b/scene/gui/rich_text_effect.cpp @@ -64,6 +64,9 @@ RichTextEffect::RichTextEffect() { } void CharFXTransform::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_transform"), &CharFXTransform::get_transform); + ClassDB::bind_method(D_METHOD("set_transform", "transform"), &CharFXTransform::set_transform); + ClassDB::bind_method(D_METHOD("get_range"), &CharFXTransform::get_range); ClassDB::bind_method(D_METHOD("set_range", "range"), &CharFXTransform::set_range); @@ -100,6 +103,7 @@ void CharFXTransform::_bind_methods() { ClassDB::bind_method(D_METHOD("get_font"), &CharFXTransform::get_font); ClassDB::bind_method(D_METHOD("set_font", "font"), &CharFXTransform::set_font); + ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "transform"), "set_transform", "get_transform"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "range"), "set_range", "get_range"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "elapsed_time"), "set_elapsed_time", "get_elapsed_time"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visibility", "is_visible"); diff --git a/scene/gui/rich_text_effect.h b/scene/gui/rich_text_effect.h index 4befdd182f..681f068fb2 100644 --- a/scene/gui/rich_text_effect.h +++ b/scene/gui/rich_text_effect.h @@ -41,6 +41,7 @@ protected: static void _bind_methods(); public: + Transform2D transform; Vector2i range; bool visibility = true; bool outline = false; @@ -57,6 +58,9 @@ public: CharFXTransform(); ~CharFXTransform(); + void set_transform(const Transform2D &p_transform) { transform = p_transform; } + const Transform2D &get_transform() { return transform; } + Vector2i get_range() { return range; } void set_range(const Vector2i &p_range) { range = p_range; } diff --git a/scene/gui/rich_text_label.compat.inc b/scene/gui/rich_text_label.compat.inc new file mode 100644 index 0000000000..a23c3b94f8 --- /dev/null +++ b/scene/gui/rich_text_label.compat.inc @@ -0,0 +1,41 @@ +/**************************************************************************/ +/* rich_text_label.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +void RichTextLabel::_add_image_bind_compat_80410(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region) { + add_image(p_image, p_width, p_height, p_color, p_alignment, p_region, Variant(), false, String(), false); +} + +void RichTextLabel::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region"), &RichTextLabel::_add_image_bind_compat_80410, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2())); +} + +#endif // DISABLE_DEPRECATED diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 96e338544c..b4cd201e6a 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "rich_text_label.h" +#include "rich_text_label.compat.inc" #include "core/input/input_map.h" #include "core/math/math_defs.h" @@ -36,6 +37,7 @@ #include "core/os/os.h" #include "core/string/translation.h" #include "scene/gui/label.h" +#include "scene/gui/rich_text_effect.h" #include "scene/resources/atlas_texture.h" #include "scene/scene_string_names.h" #include "scene/theme/theme_db.h" @@ -46,6 +48,18 @@ #include "modules/regex/regex.h" #endif +RichTextLabel::ItemCustomFX::ItemCustomFX() { + type = ITEM_CUSTOMFX; + char_fx_transform.instantiate(); +} + +RichTextLabel::ItemCustomFX::~ItemCustomFX() { + _clear_children(); + + char_fx_transform.unref(); + custom_effect.unref(); +} + RichTextLabel::Item *RichTextLabel::_get_next_item(Item *p_item, bool p_free) const { if (p_free) { if (p_item->subitems.size()) { @@ -288,6 +302,14 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { switch (it->type) { + case ITEM_IMAGE: { + ItemImage *img = static_cast<ItemImage *>(it); + Size2 img_size = img->size; + if (img->size_in_percent) { + img_size = _get_image_size(img->image, p_width * img->rq_size.width / 100.f, p_width * img->rq_size.height / 100.f, img->region); + l.text_buf->resize_object((uint64_t)it, img_size, img->inline_align, 1); + } + } break; case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); int col_count = table->columns.size(); @@ -562,7 +584,11 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } break; case ITEM_IMAGE: { ItemImage *img = static_cast<ItemImage *>(it); - l.text_buf->add_object((uint64_t)it, img->size, img->inline_align, 1); + Size2 img_size = img->size; + if (img->size_in_percent) { + img_size = _get_image_size(img->image, p_width * img->rq_size.width / 100.f, p_width * img->rq_size.height / 100.f, img->region); + } + l.text_buf->add_object((uint64_t)it, img_size, img->inline_align, 1); txt += String::chr(0xfffc); l.char_count++; remaining_characters--; @@ -920,7 +946,13 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o switch (it->type) { case ITEM_IMAGE: { ItemImage *img = static_cast<ItemImage *>(it); - img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off, rect.size), false, img->color); + if (img->pad) { + Size2 pad_size = Size2(MIN(rect.size.x, img->image->get_width()), MIN(rect.size.y, img->image->get_height())); + Vector2 pad_off = (rect.size - pad_size) / 2; + img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off + pad_off, pad_size), false, img->color); + } else { + img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off, rect.size), false, img->color); + } } break; case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); @@ -1028,6 +1060,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } bool txt_visible = (font_outline_color.a != 0) || (font_shadow_color.a != 0); + Transform2D char_xform; + char_xform.set_origin(gloff + p_ofs); for (int j = 0; j < fx_stack.size(); j++) { ItemFX *item_fx = fx_stack[j]; @@ -1051,10 +1085,12 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o charfx->glyph_count = gl_cn; charfx->offset = fx_offset; charfx->color = font_color; + charfx->transform = char_xform; bool effect_status = custom_effect->_process_effect_impl(charfx); custom_fx_ok = effect_status; + char_xform = charfx->transform; fx_offset += charfx->offset; font_color = charfx->color; frid = charfx->font; @@ -1108,6 +1144,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o fx_offset = fx_offset.round(); } + Vector2i char_off = char_xform.get_origin(); + // Draw glyph outlines. const Color modulated_outline_color = font_outline_color * Color(1, 1, 1, font_color.a); const Color modulated_shadow_color = font_shadow_color * Color(1, 1, 1, font_color.a); @@ -1116,13 +1154,24 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs)); if (!skip && frid != RID()) { if (modulated_shadow_color.a > 0) { - TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + gloff + p_shadow_ofs, gl, modulated_shadow_color); - } - if (modulated_shadow_color.a > 0 && p_shadow_outline_size > 0) { - TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, p_shadow_outline_size, p_ofs + fx_offset + gloff + p_shadow_ofs, gl, modulated_shadow_color); + Transform2D char_reverse_xform; + char_reverse_xform.set_origin(-char_off - p_shadow_ofs); + Transform2D char_final_xform = char_xform * char_reverse_xform; + char_final_xform.columns[2] += p_shadow_ofs; + draw_set_transform_matrix(char_final_xform); + + TS->font_draw_glyph(frid, ci, glyphs[i].font_size, fx_offset + char_off + p_shadow_ofs, gl, modulated_shadow_color); + if (p_shadow_outline_size > 0) { + TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, p_shadow_outline_size, fx_offset + char_off + p_shadow_ofs, gl, modulated_shadow_color); + } } if (modulated_outline_color.a != 0.0 && size > 0) { - TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff, gl, modulated_outline_color); + Transform2D char_reverse_xform; + char_reverse_xform.set_origin(-char_off); + Transform2D char_final_xform = char_xform * char_reverse_xform; + draw_set_transform_matrix(char_final_xform); + + TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, fx_offset + char_off, gl, modulated_outline_color); } } processed_glyphs_ol++; @@ -1130,6 +1179,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o gloff.x += glyphs[i].advance; } } + draw_set_transform_matrix(Transform2D()); Vector2 fbg_line_off = off + p_ofs; // Draw background color box @@ -1256,6 +1306,9 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o bool txt_visible = (font_color.a != 0); + Transform2D char_xform; + char_xform.set_origin(p_ofs + off); + for (int j = 0; j < fx_stack.size(); j++) { ItemFX *item_fx = fx_stack[j]; bool cn = cprev_cluster || (cprev_conn && item_fx->connected); @@ -1278,10 +1331,12 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o charfx->glyph_count = gl_cn; charfx->offset = fx_offset; charfx->color = font_color; + charfx->transform = char_xform; bool effect_status = custom_effect->_process_effect_impl(charfx); custom_fx_ok = effect_status; + char_xform = charfx->transform; fx_offset += charfx->offset; font_color = charfx->color; frid = charfx->font; @@ -1335,6 +1390,12 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o fx_offset = fx_offset.round(); } + Vector2i char_off = char_xform.get_origin(); + Transform2D char_reverse_xform; + char_reverse_xform.set_origin(-char_off); + char_xform = char_xform * char_reverse_xform; + draw_set_transform_matrix(char_xform); + if (selected && use_selected_font_color) { font_color = theme_cache.font_selected_color; } @@ -1345,9 +1406,9 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (txt_visible) { if (!skip) { if (frid != RID()) { - TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color); + TS->font_draw_glyph(frid, ci, glyphs[i].font_size, fx_offset + char_off, gl, font_color); } else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) { - TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color); + TS->draw_hex_code_box(ci, glyphs[i].font_size, fx_offset + char_off, gl, font_color); } } r_processed_glyphs++; @@ -1375,6 +1436,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } off.x += glyphs[i].advance; } + + draw_set_transform_matrix(Transform2D()); } if (ul_started) { ul_started = false; @@ -2171,11 +2234,15 @@ String RichTextLabel::get_tooltip(const Point2 &p_pos) const { const_cast<RichTextLabel *>(this)->_find_click(main, p_pos, nullptr, nullptr, &c_item, nullptr, &outside, true); String description; - if (c_item && !outside && const_cast<RichTextLabel *>(this)->_find_hint(c_item, &description)) { - return description; - } else { - return Control::get_tooltip(p_pos); + if (c_item && !outside) { + if (const_cast<RichTextLabel *>(this)->_find_hint(c_item, &description)) { + return description; + } else if (c_item->type == ITEM_IMAGE && !static_cast<ItemImage *>(c_item)->tooltip.is_empty()) { + return static_cast<ItemImage *>(c_item)->tooltip; + } } + + return Control::get_tooltip(p_pos); } void RichTextLabel::_find_frame(Item *p_item, ItemFrame **r_frame, int *r_line) { @@ -3077,69 +3144,157 @@ void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_sub memdelete(p_item); } -void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region) { - _stop_thread(); - MutexLock data_lock(data_mutex); - - if (current->type == ITEM_TABLE) { - return; - } - - ERR_FAIL_COND(p_image.is_null()); - ERR_FAIL_COND(p_image->get_width() == 0); - ERR_FAIL_COND(p_image->get_height() == 0); - ItemImage *item = memnew(ItemImage); - - if (p_region.has_area()) { - Ref<AtlasTexture> atlas_tex = memnew(AtlasTexture); - atlas_tex->set_atlas(p_image); - atlas_tex->set_region(p_region); - item->image = atlas_tex; - } else { - item->image = p_image; - } - - item->color = p_color; - item->inline_align = p_alignment; - +Size2 RichTextLabel::_get_image_size(const Ref<Texture2D> &p_image, int p_width, int p_height, const Rect2 &p_region) { + Size2 ret; if (p_width > 0) { // custom width - item->size.width = p_width; + ret.width = p_width; if (p_height > 0) { // custom height - item->size.height = p_height; + ret.height = p_height; } else { // calculate height to keep aspect ratio if (p_region.has_area()) { - item->size.height = p_region.get_size().height * p_width / p_region.get_size().width; + ret.height = p_region.get_size().height * p_width / p_region.get_size().width; } else { - item->size.height = p_image->get_height() * p_width / p_image->get_width(); + ret.height = p_image->get_height() * p_width / p_image->get_width(); } } } else { if (p_height > 0) { // custom height - item->size.height = p_height; + ret.height = p_height; // calculate width to keep aspect ratio if (p_region.has_area()) { - item->size.width = p_region.get_size().width * p_height / p_region.get_size().height; + ret.width = p_region.get_size().width * p_height / p_region.get_size().height; } else { - item->size.width = p_image->get_width() * p_height / p_image->get_height(); + ret.width = p_image->get_width() * p_height / p_image->get_height(); } } else { if (p_region.has_area()) { // if the image has a region, keep the region size - item->size = p_region.get_size(); + ret = p_region.get_size(); } else { // keep original width and height - item->size = p_image->get_size(); + ret = p_image->get_size(); } } } + return ret; +} + +void RichTextLabel::add_image(const Ref<Texture2D> &p_image, int p_width, int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region, const Variant &p_key, bool p_pad, const String &p_tooltip, bool p_size_in_percent) { + _stop_thread(); + MutexLock data_lock(data_mutex); + + if (current->type == ITEM_TABLE) { + return; + } + + ERR_FAIL_COND(p_image.is_null()); + ERR_FAIL_COND(p_image->get_width() == 0); + ERR_FAIL_COND(p_image->get_height() == 0); + ItemImage *item = memnew(ItemImage); + + if (p_region.has_area()) { + Ref<AtlasTexture> atlas_tex = memnew(AtlasTexture); + atlas_tex->set_atlas(p_image); + atlas_tex->set_region(p_region); + item->image = atlas_tex; + } else { + item->image = p_image; + } + item->color = p_color; + item->inline_align = p_alignment; + item->rq_size = Size2(p_width, p_height); + item->region = p_region; + item->size = _get_image_size(p_image, p_width, p_height, p_region); + item->size_in_percent = p_size_in_percent; + item->pad = p_pad; + item->key = p_key; + item->tooltip = p_tooltip; _add_item(item, false); } +void RichTextLabel::update_image(const Variant &p_key, BitField<ImageUpdateMask> p_mask, const Ref<Texture2D> &p_image, int p_width, int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region, bool p_pad, const String &p_tooltip, bool p_size_in_percent) { + _stop_thread(); + MutexLock data_lock(data_mutex); + + if (p_mask & UPDATE_TEXTURE) { + ERR_FAIL_COND(p_image.is_null()); + ERR_FAIL_COND(p_image->get_width() == 0); + ERR_FAIL_COND(p_image->get_height() == 0); + } + + bool reshape = false; + + Item *it = main; + while (it) { + if (it->type == ITEM_IMAGE) { + ItemImage *it_img = static_cast<ItemImage *>(it); + if (it_img->key == p_key) { + ItemImage *item = it_img; + if (p_mask & UPDATE_REGION) { + item->region = p_region; + } + if (p_mask & UPDATE_TEXTURE) { + if (item->region.has_area()) { + Ref<AtlasTexture> atlas_tex = memnew(AtlasTexture); + atlas_tex->set_atlas(p_image); + atlas_tex->set_region(item->region); + item->image = atlas_tex; + } else { + item->image = p_image; + } + } + if (p_mask & UPDATE_COLOR) { + item->color = p_color; + } + if (p_mask & UPDATE_TOOLTIP) { + item->tooltip = p_tooltip; + } + if (p_mask & UPDATE_PAD) { + item->pad = p_pad; + } + if (p_mask & UPDATE_ALIGNMENT) { + if (item->inline_align != p_alignment) { + reshape = true; + item->inline_align = p_alignment; + } + } + if (p_mask & UPDATE_WIDTH_IN_PERCENT) { + if (item->size_in_percent != p_size_in_percent) { + reshape = true; + item->size_in_percent = p_size_in_percent; + } + } + if (p_mask & UPDATE_SIZE) { + if (p_width > 0) { + item->rq_size.width = p_width; + } + if (p_height > 0) { + item->rq_size.height = p_height; + } + } + if ((p_mask & UPDATE_SIZE) || (p_mask & UPDATE_REGION) || (p_mask & UPDATE_TEXTURE)) { + Size2 new_size = _get_image_size(item->image, item->rq_size.width, item->rq_size.height, item->region); + if (item->size != new_size) { + reshape = true; + item->size = new_size; + } + } + } + } + it = _get_next_item(it, true); + } + + if (reshape) { + main->first_invalid_line.store(0); + } + queue_redraw(); +} + void RichTextLabel::add_newline() { _stop_thread(); MutexLock data_lock(data_mutex); @@ -4435,6 +4590,9 @@ void RichTextLabel::append_text(const String &p_bbcode) { int width = 0; int height = 0; + bool pad = false; + String tooltip; + bool size_in_percent = false; if (!bbcode_value.is_empty()) { int sep = bbcode_value.find("x"); if (sep == -1) { @@ -4447,15 +4605,31 @@ void RichTextLabel::append_text(const String &p_bbcode) { OptionMap::Iterator width_option = bbcode_options.find("width"); if (width_option) { width = width_option->value.to_int(); + if (width_option->value.ends_with("%")) { + size_in_percent = true; + } } OptionMap::Iterator height_option = bbcode_options.find("height"); if (height_option) { height = height_option->value.to_int(); + if (height_option->value.ends_with("%")) { + size_in_percent = true; + } + } + + OptionMap::Iterator tooltip_option = bbcode_options.find("tooltip"); + if (tooltip_option) { + tooltip = tooltip_option->value; + } + + OptionMap::Iterator pad_option = bbcode_options.find("pad"); + if (pad_option) { + pad = (pad_option->value == "true"); } } - add_image(texture, width, height, color, (InlineAlignment)alignment, region); + add_image(texture, width, height, color, (InlineAlignment)alignment, region, Variant(), pad, tooltip, size_in_percent); } pos = end; @@ -5580,7 +5754,8 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_parsed_text"), &RichTextLabel::get_parsed_text); ClassDB::bind_method(D_METHOD("add_text", "text"), &RichTextLabel::add_text); ClassDB::bind_method(D_METHOD("set_text", "text"), &RichTextLabel::set_text); - ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2(0, 0, 0, 0))); + ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region", "key", "pad", "tooltip", "size_in_percent"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()), DEFVAL(Variant()), DEFVAL(false), DEFVAL(String()), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("update_image", "key", "mask", "image", "width", "height", "color", "inline_align", "region", "pad", "tooltip", "size_in_percent"), &RichTextLabel::update_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()), DEFVAL(false), DEFVAL(String()), DEFVAL(false)); ClassDB::bind_method(D_METHOD("newline"), &RichTextLabel::add_newline); ClassDB::bind_method(D_METHOD("remove_paragraph", "paragraph"), &RichTextLabel::remove_paragraph); ClassDB::bind_method(D_METHOD("push_font", "font", "font_size"), &RichTextLabel::push_font, DEFVAL(0)); @@ -5788,6 +5963,15 @@ void RichTextLabel::_bind_methods() { BIND_ENUM_CONSTANT(MENU_SELECT_ALL); BIND_ENUM_CONSTANT(MENU_MAX); + BIND_BITFIELD_FLAG(UPDATE_TEXTURE); + BIND_BITFIELD_FLAG(UPDATE_SIZE); + BIND_BITFIELD_FLAG(UPDATE_COLOR); + BIND_BITFIELD_FLAG(UPDATE_ALIGNMENT); + BIND_BITFIELD_FLAG(UPDATE_REGION); + BIND_BITFIELD_FLAG(UPDATE_PAD); + BIND_BITFIELD_FLAG(UPDATE_TOOLTIP); + BIND_BITFIELD_FLAG(UPDATE_WIDTH_IN_PERCENT); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, RichTextLabel, normal_style, "normal"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, RichTextLabel, focus_style, "focus"); BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_STYLEBOX, RichTextLabel, progress_bg_style, "background", "ProgressBar"); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index d88623073d..2f48600583 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -33,10 +33,12 @@ #include "core/object/worker_thread_pool.h" #include "scene/gui/popup_menu.h" -#include "scene/gui/rich_text_effect.h" #include "scene/gui/scroll_bar.h" #include "scene/resources/text_paragraph.h" +class CharFXTransform; +class RichTextEffect; + class RichTextLabel : public Control { GDCLASS(RichTextLabel, Control); @@ -96,12 +98,28 @@ public: CUSTOM_FONT, }; + enum ImageUpdateMask { + UPDATE_TEXTURE = 1 << 0, + UPDATE_SIZE = 1 << 1, + UPDATE_COLOR = 1 << 2, + UPDATE_ALIGNMENT = 1 << 3, + UPDATE_REGION = 1 << 4, + UPDATE_PAD = 1 << 5, + UPDATE_TOOLTIP = 1 << 6, + UPDATE_WIDTH_IN_PERCENT = 1 << 7, + }; + protected: virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + void _add_image_bind_compat_80410(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region); + static void _bind_compatibility_methods(); +#endif + private: struct Item; @@ -187,8 +205,14 @@ private: struct ItemImage : public Item { Ref<Texture2D> image; InlineAlignment inline_align = INLINE_ALIGNMENT_CENTER; + bool pad = false; + bool size_in_percent = false; + Rect2 region; Size2 size; + Size2 rq_size; Color color; + Variant key; + String tooltip; ItemImage() { type = ITEM_IMAGE; } }; @@ -374,17 +398,9 @@ private: Ref<CharFXTransform> char_fx_transform; Ref<RichTextEffect> custom_effect; - ItemCustomFX() { - type = ITEM_CUSTOMFX; - char_fx_transform.instantiate(); - } - - virtual ~ItemCustomFX() { - _clear_children(); + ItemCustomFX(); - char_fx_transform.unref(); - custom_effect.unref(); - } + virtual ~ItemCustomFX(); }; struct ItemContext : public Item { @@ -550,6 +566,8 @@ private: Ref<RichTextEffect> _get_custom_effect_by_code(String p_bbcode_identifier); virtual Dictionary parse_expressions_for_values(Vector<String> p_expressions); + Size2 _get_image_size(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Rect2 &p_region = Rect2()); + void _draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item *it_from, Item *it_to, int start, int end, int fbg_flag); #ifndef DISABLE_DEPRECATED // Kept for compatibility from 3.x to 4.0. @@ -607,7 +625,8 @@ private: public: String get_parsed_text() const; void add_text(const String &p_text); - void add_image(const Ref<Texture2D> &p_image, const int p_width = 0, const int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(0, 0, 0, 0)); + void add_image(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), const Variant &p_key = Variant(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false); + void update_image(const Variant &p_key, BitField<ImageUpdateMask> p_mask, const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false); void add_newline(); bool remove_paragraph(const int p_paragraph); void push_dropcap(const String &p_string, const Ref<Font> &p_font, int p_size, const Rect2 &p_dropcap_margins = Rect2(), const Color &p_color = Color(1, 1, 1), int p_ol_size = 0, const Color &p_ol_color = Color(0, 0, 0, 0)); @@ -785,5 +804,6 @@ public: VARIANT_ENUM_CAST(RichTextLabel::ListType); VARIANT_ENUM_CAST(RichTextLabel::MenuItems); +VARIANT_BITFIELD_CAST(RichTextLabel::ImageUpdateMask); #endif // RICH_TEXT_LABEL_H diff --git a/servers/physics_2d/godot_body_2d.cpp b/servers/physics_2d/godot_body_2d.cpp index bc31721ae7..7809fa9475 100644 --- a/servers/physics_2d/godot_body_2d.cpp +++ b/servers/physics_2d/godot_body_2d.cpp @@ -35,7 +35,7 @@ #include "godot_space_2d.h" void GodotBody2D::_mass_properties_changed() { - if (get_space() && !mass_properties_update_list.in_list() && (calculate_inertia || calculate_center_of_mass)) { + if (get_space() && !mass_properties_update_list.in_list()) { get_space()->body_add_to_mass_properties_update_list(&mass_properties_update_list); } } diff --git a/servers/physics_3d/godot_body_3d.cpp b/servers/physics_3d/godot_body_3d.cpp index 3c93fee4f8..d35f16f3ae 100644 --- a/servers/physics_3d/godot_body_3d.cpp +++ b/servers/physics_3d/godot_body_3d.cpp @@ -35,7 +35,7 @@ #include "godot_space_3d.h" void GodotBody3D::_mass_properties_changed() { - if (get_space() && !mass_properties_update_list.in_list() && (calculate_inertia || calculate_center_of_mass)) { + if (get_space() && !mass_properties_update_list.in_list()) { get_space()->body_add_to_mass_properties_update_list(&mass_properties_update_list); } } diff --git a/tests/core/math/test_basis.h b/tests/core/math/test_basis.h index 0a34954bd3..fcac9a6231 100644 --- a/tests/core/math/test_basis.h +++ b/tests/core/math/test_basis.h @@ -296,6 +296,36 @@ TEST_CASE("[Basis] Finite number checks") { "Basis with three components infinite should not be finite."); } +TEST_CASE("[Basis] Is conformal checks") { + CHECK_MESSAGE( + Basis().is_conformal(), + "Identity Basis should be conformal."); + + CHECK_MESSAGE( + Basis::from_euler(Vector3(1.2, 3.4, 5.6)).is_conformal(), + "Basis with only rotation should be conformal."); + + CHECK_MESSAGE( + Basis::from_scale(Vector3(-1, -1, -1)).is_conformal(), + "Basis with only a flip should be conformal."); + + CHECK_MESSAGE( + Basis::from_scale(Vector3(1.2, 1.2, 1.2)).is_conformal(), + "Basis with only uniform scale should be conformal."); + + CHECK_MESSAGE( + Basis(Vector3(3, 4, 0), Vector3(4, -3, 0.0), Vector3(0, 0, 5)).is_conformal(), + "Basis with a flip, rotation, and uniform scale should be conformal."); + + CHECK_FALSE_MESSAGE( + Basis::from_scale(Vector3(1.2, 3.4, 5.6)).is_conformal(), + "Basis with non-uniform scale should not be conformal."); + + CHECK_FALSE_MESSAGE( + Basis(Vector3(Math_SQRT12, Math_SQRT12, 0), Vector3(0, 1, 0), Vector3(0, 0, 1)).is_conformal(), + "Basis with the X axis skewed 45 degrees should not be conformal."); +} + } // namespace TestBasis #endif // TEST_BASIS_H diff --git a/tests/core/math/test_transform_2d.h b/tests/core/math/test_transform_2d.h index ca27776180..36d27ce7a9 100644 --- a/tests/core/math/test_transform_2d.h +++ b/tests/core/math/test_transform_2d.h @@ -130,6 +130,36 @@ TEST_CASE("[Transform2D] Finite number checks") { "Transform2D with three components infinite should not be finite."); } +TEST_CASE("[Transform2D] Is conformal checks") { + CHECK_MESSAGE( + Transform2D().is_conformal(), + "Identity Transform2D should be conformal."); + + CHECK_MESSAGE( + Transform2D(1.2, Vector2()).is_conformal(), + "Transform2D with only rotation should be conformal."); + + CHECK_MESSAGE( + Transform2D(Vector2(1, 0), Vector2(0, -1), Vector2()).is_conformal(), + "Transform2D with only a flip should be conformal."); + + CHECK_MESSAGE( + Transform2D(Vector2(1.2, 0), Vector2(0, 1.2), Vector2()).is_conformal(), + "Transform2D with only uniform scale should be conformal."); + + CHECK_MESSAGE( + Transform2D(Vector2(1.2, 3.4), Vector2(3.4, -1.2), Vector2()).is_conformal(), + "Transform2D with a flip, rotation, and uniform scale should be conformal."); + + CHECK_FALSE_MESSAGE( + Transform2D(Vector2(1.2, 0), Vector2(0, 3.4), Vector2()).is_conformal(), + "Transform2D with non-uniform scale should not be conformal."); + + CHECK_FALSE_MESSAGE( + Transform2D(Vector2(Math_SQRT12, Math_SQRT12), Vector2(0, 1), Vector2()).is_conformal(), + "Transform2D with the X axis skewed 45 degrees should not be conformal."); +} + } // namespace TestTransform2D #endif // TEST_TRANSFORM_2D_H |