summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/math/basis.cpp8
-rw-r--r--core/math/basis.h1
-rw-r--r--core/math/transform_2d.cpp12
-rw-r--r--core/math/transform_2d.h1
-rw-r--r--core/object/class_db.cpp62
-rw-r--r--core/object/class_db.h1
-rw-r--r--core/variant/variant_call.cpp2
-rw-r--r--doc/classes/Basis.xml6
-rw-r--r--doc/classes/CharFXTransform.xml3
-rw-r--r--doc/classes/RichTextLabel.xml48
-rw-r--r--doc/classes/Transform2D.xml6
-rw-r--r--editor/editor_help.cpp38
-rw-r--r--methods.py2
-rw-r--r--misc/extension_api_validation/4.1-stable.expected7
-rw-r--r--modules/mono/editor/bindings_generator.cpp110
-rw-r--r--modules/mono/editor/bindings_generator.h14
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Compat.cs7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs12
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs3
-rw-r--r--modules/mono/glue/runtime_interop.cpp5
-rw-r--r--scene/gui/rich_text_effect.cpp4
-rw-r--r--scene/gui/rich_text_effect.h4
-rw-r--r--scene/gui/rich_text_label.compat.inc41
-rw-r--r--scene/gui/rich_text_label.cpp282
-rw-r--r--scene/gui/rich_text_label.h44
-rw-r--r--servers/physics_2d/godot_body_2d.cpp2
-rw-r--r--servers/physics_3d/godot_body_3d.cpp2
-rw-r--r--tests/core/math/test_basis.h30
-rw-r--r--tests/core/math/test_transform_2d.h30
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="&quot;&quot;" />
+ <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="&quot;&quot;" />
+ <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