Kaynağa Gözat

[RTL] Decouple image width/height "in percent" properties. Add [hr] tag support.

Pāvels Nadtočajevs 1 ay önce
ebeveyn
işleme
a262747cdf

+ 24 - 4
doc/classes/RichTextLabel.xml

@@ -17,6 +17,20 @@
 		<link title="Operating System Testing Demo">https://godotengine.org/asset-library/asset/2789</link>
 	</tutorials>
 	<methods>
+		<method name="add_hr">
+			<return type="void" />
+			<param index="0" name="width" type="int" default="90" />
+			<param index="1" name="height" type="int" default="2" />
+			<param index="2" name="color" type="Color" default="Color(1, 1, 1, 1)" />
+			<param index="3" name="alignment" type="int" enum="HorizontalAlignment" default="1" />
+			<param index="4" name="width_in_percent" type="bool" default="true" />
+			<param index="5" name="height_in_percent" type="bool" default="false" />
+			<description>
+				Adds a horizontal rule that can be used to separate content.
+				If [param width_in_percent] is set, [param width] values are percentages of the control width instead of pixels.
+				If [param height_in_percent] is set, [param height] values are percentages of the control width instead of pixels.
+			</description>
+		</method>
 		<method name="add_image">
 			<return type="void" />
 			<param index="0" name="image" type="Texture2D" />
@@ -28,15 +42,17 @@
 			<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" />
-			<param index="10" name="alt_text" type="String" default="&quot;&quot;" />
+			<param index="9" name="width_in_percent" type="bool" default="false" />
+			<param index="10" name="height_in_percent" type="bool" default="false" />
+			<param index="11" name="alt_text" type="String" default="&quot;&quot;" />
 			<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.
+				If [param width_in_percent] is set, [param width] values are percentages of the control width instead of pixels.
+				If [param height_in_percent] is set, [param height] values are percentages of the control width instead of pixels.
 				[param alt_text] is used as the image description for assistive apps.
 			</description>
 		</method>
@@ -655,7 +671,8 @@
 			<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" />
+			<param index="10" name="width_in_percent" type="bool" default="false" />
+			<param index="11" name="height_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>
@@ -939,6 +956,9 @@
 		<theme_item name="normal_font_size" data_type="font_size" type="int">
 			The default text font size.
 		</theme_item>
+		<theme_item name="horizontal_rule" data_type="icon" type="Texture2D">
+			The horizontal rule texture.
+		</theme_item>
 		<theme_item name="focus" data_type="style" type="StyleBox">
 			The background used when the [RichTextLabel] is focused. The [theme_item focus] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
 		</theme_item>

+ 9 - 0
misc/extension_api_validation/4.4-stable.expected

@@ -312,3 +312,12 @@ Validate extension JSON: Error: Field 'classes/GLTFBufferView/methods/set_byte_o
 Validate extension JSON: Error: Field 'classes/GLTFBufferView/methods/set_byte_stride/arguments/0': meta changed value in new API, from "int32" to "int64".
 
 GLTFBufferView and GLTFAccessor now use int64 for offsets and lengths. Compatibility methods registered.
+
+
+GH-107347
+---------
+Validate extension JSON: Error: Field 'classes/RichTextLabel/methods/add_image/arguments': size changed value in new API, from 6 to 12.
+Validate extension JSON: Error: Field 'classes/RichTextLabel/methods/add_image/arguments': size changed value in new API, from 10 to 12.
+Validate extension JSON: Error: Field 'classes/RichTextLabel/methods/update_image/arguments': size changed value in new API, from 11 to 12.
+
+Optional argument added. Compatibility methods registered.

+ 12 - 2
scene/gui/rich_text_label.compat.inc

@@ -51,11 +51,11 @@ void RichTextLabel::_push_meta_bind_compat_89024(const Variant &p_meta) {
 }
 
 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, String());
+	add_image(p_image, p_width, p_height, p_color, p_alignment, p_region, Variant(), false, String(), false, false, String());
 }
 
 void RichTextLabel::_add_image_bind_compat_76829(const Ref<Texture2D> &p_image, const int p_width, const 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) {
-	add_image(p_image, p_width, p_height, p_color, p_alignment, p_region, p_key, p_pad, p_tooltip, p_size_in_percent, String());
+	add_image(p_image, p_width, p_height, p_color, p_alignment, p_region, p_key, p_pad, p_tooltip, p_size_in_percent, p_size_in_percent, String());
 }
 
 void RichTextLabel::_push_table_bind_compat_76829(int p_columns, InlineAlignment p_alignment, int p_align_to_row) {
@@ -74,6 +74,14 @@ void RichTextLabel::_push_strikethrough_bind_compat_106300() {
 	push_strikethrough(Color(0, 0, 0, 0));
 }
 
+void RichTextLabel::_add_image_bind_compat_107347(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, const String &p_alt_text) {
+	add_image(p_image, p_width, p_height, p_color, p_alignment, p_region, p_key, p_pad, p_tooltip, p_size_in_percent, p_size_in_percent, p_alt_text);
+}
+
+void RichTextLabel::_update_image_bind_compat_107347(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) {
+	update_image(p_key, p_mask, p_image, p_width, p_height, p_color, p_alignment, p_region, p_pad, p_tooltip, p_size_in_percent, p_size_in_percent);
+}
+
 void RichTextLabel::_bind_compatibility_methods() {
 	ClassDB::bind_compatibility_method(D_METHOD("push_font", "font", "font_size"), &RichTextLabel::_push_font_bind_compat_79053);
 	ClassDB::bind_compatibility_method(D_METHOD("set_table_column_expand", "column", "expand", "ratio"), &RichTextLabel::_set_table_column_expand_bind_compat_79053);
@@ -86,6 +94,8 @@ void RichTextLabel::_bind_compatibility_methods() {
 	ClassDB::bind_compatibility_method(D_METHOD("remove_paragraph", "paragraph"), &RichTextLabel::_remove_paragraph_bind_compat_91098);
 	ClassDB::bind_compatibility_method(D_METHOD("push_underline"), &RichTextLabel::_push_underline_bind_compat_106300);
 	ClassDB::bind_compatibility_method(D_METHOD("push_strikethrough"), &RichTextLabel::_push_strikethrough_bind_compat_106300);
+	ClassDB::bind_compatibility_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region", "key", "pad", "tooltip", "size_in_percent", "alt_text"), &RichTextLabel::_add_image_bind_compat_107347, 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), DEFVAL(String()));
+	ClassDB::bind_compatibility_method(D_METHOD("update_image", "key", "mask", "image", "width", "height", "color", "inline_align", "region", "pad", "tooltip", "size_in_percent"), &RichTextLabel::_update_image_bind_compat_107347, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()), DEFVAL(false), DEFVAL(String()), DEFVAL(false));
 }
 
 #endif // DISABLE_DEPRECATED

+ 116 - 15
scene/gui/rich_text_label.cpp

@@ -383,8 +383,8 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font
 			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);
+				if (img->width_in_percent || img->height_in_percent) {
+					img_size = _get_image_size(img->image, img->width_in_percent ? (p_width * img->rq_size.width / 100.f) : img->rq_size.width, img->height_in_percent ? (p_width * img->rq_size.height / 100.f) : img->rq_size.height, img->region);
 					l.text_buf->resize_object(it->rid, img_size, img->inline_align);
 				}
 			} break;
@@ -591,8 +591,8 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
 			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);
+				if (img->width_in_percent || img->height_in_percent) {
+					img_size = _get_image_size(img->image, img->width_in_percent ? (p_width * img->rq_size.width / 100.f) : img->rq_size.width, img->height_in_percent ? (p_width * img->rq_size.height / 100.f) : img->rq_size.height, img->region);
 				}
 				l.text_buf->add_object(it->rid, img_size, img->inline_align, 1);
 				txt += String::chr(0xfffc);
@@ -2346,6 +2346,21 @@ void RichTextLabel::_notification(int p_what) {
 		case NOTIFICATION_THEME_CHANGED: {
 			_stop_thread();
 			main->first_invalid_font_line.store(0); // Invalidate all lines.
+			for (const RID &E : hr_list) {
+				Item *it = items.get_or_null(E);
+				if (it) {
+					ItemImage *img = static_cast<ItemImage *>(it);
+					if (img) {
+						if (img->image.is_valid()) {
+							img->image->disconnect_changed(callable_mp(this, &RichTextLabel::_texture_changed));
+						}
+						img->image = theme_cache.horizontal_rule;
+						if (img->image.is_valid()) {
+							img->image->connect_changed(callable_mp(this, &RichTextLabel::_texture_changed).bind(img->rid), CONNECT_REFERENCE_COUNTED);
+						}
+					}
+				}
+			}
 			_invalidate_accessibility();
 			queue_accessibility_update();
 			queue_redraw();
@@ -3952,7 +3967,50 @@ Size2 RichTextLabel::_get_image_size(const Ref<Texture2D> &p_image, int p_width,
 	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, const String &p_alt_text) {
+void RichTextLabel::add_hr(int p_width, int p_height, const Color &p_color, HorizontalAlignment p_alignment, bool p_width_in_percent, bool p_height_in_percent) {
+	_stop_thread();
+	MutexLock data_lock(data_mutex);
+
+	if (current->type == ITEM_TABLE) {
+		return;
+	}
+
+	ERR_FAIL_COND(p_width < 0);
+	ERR_FAIL_COND(p_height < 0);
+
+	ItemParagraph *p_item = memnew(ItemParagraph);
+	p_item->owner = get_instance_id();
+	p_item->rid = items.make_rid(p_item);
+	p_item->alignment = p_alignment;
+	_add_item(p_item, true, true);
+
+	ItemImage *item = memnew(ItemImage);
+	item->owner = get_instance_id();
+	item->rid = items.make_rid(item);
+
+	item->image = theme_cache.horizontal_rule;
+	item->color = p_color;
+	item->inline_align = INLINE_ALIGNMENT_CENTER;
+	item->rq_size = Size2(p_width, p_height);
+	item->size = _get_image_size(theme_cache.horizontal_rule, p_width, p_height, Rect2());
+	item->width_in_percent = p_width_in_percent;
+	item->height_in_percent = p_height_in_percent;
+
+	item->image->connect_changed(callable_mp(this, &RichTextLabel::_texture_changed).bind(item->rid), CONNECT_REFERENCE_COUNTED);
+
+	_add_item(item, false);
+	hr_list.insert(item->rid);
+
+	if (current->type == ITEM_FRAME) {
+		current_frame = static_cast<ItemFrame *>(current)->parent_frame;
+	}
+	current = current->parent;
+	if (!parsing_bbcode.load() && !tag_stack.is_empty()) {
+		tag_stack.pop_back();
+	}
+}
+
+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_width_in_percent, bool p_height_in_percent, const String &p_alt_text) {
 	_stop_thread();
 	MutexLock data_lock(data_mutex);
 
@@ -3983,7 +4041,8 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, int p_width, int p_
 	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->width_in_percent = p_width_in_percent;
+	item->height_in_percent = p_height_in_percent;
 	item->pad = p_pad;
 	item->key = p_key;
 	item->tooltip = p_tooltip;
@@ -3995,7 +4054,7 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, int p_width, int p_
 	update_configuration_warnings();
 }
 
-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) {
+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_width_in_percent, bool p_height_in_percent) {
 	_stop_thread();
 	MutexLock data_lock(data_mutex);
 
@@ -4056,9 +4115,10 @@ void RichTextLabel::update_image(const Variant &p_key, BitField<ImageUpdateMask>
 					}
 				}
 				if (p_mask & UPDATE_WIDTH_IN_PERCENT) {
-					if (item->size_in_percent != p_size_in_percent) {
+					if (item->width_in_percent != p_width_in_percent || item->height_in_percent != p_height_in_percent) {
 						reshape = true;
-						item->size_in_percent = p_size_in_percent;
+						item->width_in_percent = p_width_in_percent;
+						item->height_in_percent = p_height_in_percent;
 					}
 				}
 				if (p_mask & UPDATE_SIZE) {
@@ -5676,6 +5736,43 @@ void RichTextLabel::append_text(const String &p_bbcode) {
 
 			pos = end;
 			tag_stack.push_front(bbcode_name);
+		} else if (tag.begins_with("hr")) {
+			HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_CENTER;
+			OptionMap::Iterator align_option = bbcode_options.find("align");
+			if (align_option) {
+				if (align_option->value == "l" || align_option->value == "left") {
+					alignment = HORIZONTAL_ALIGNMENT_LEFT;
+				} else if (align_option->value == "c" || align_option->value == "center") {
+					alignment = HORIZONTAL_ALIGNMENT_CENTER;
+				} else if (align_option->value == "r" || align_option->value == "right") {
+					alignment = HORIZONTAL_ALIGNMENT_RIGHT;
+				}
+			}
+
+			Color color = theme_cache.default_color;
+			OptionMap::Iterator color_option = bbcode_options.find("color");
+			if (color_option) {
+				color = Color::from_string(color_option->value, color);
+			}
+			int width = 90;
+			bool width_in_percent = true;
+			OptionMap::Iterator width_option = bbcode_options.find("width");
+			if (width_option) {
+				width = width_option->value.to_int();
+				width_in_percent = (width_option->value.ends_with("%"));
+			}
+
+			int height = 2;
+			bool height_in_percent = false;
+			OptionMap::Iterator height_option = bbcode_options.find("height");
+			if (height_option) {
+				height = height_option->value.to_int();
+				height_in_percent = (height_option->value.ends_with("%"));
+			}
+
+			add_hr(width, height, color, alignment, width_in_percent, height_in_percent);
+
+			pos = brk_end + 1;
 		} else if (tag.begins_with("img")) {
 			int alignment = INLINE_ALIGNMENT_CENTER;
 			if (tag.begins_with("img=")) {
@@ -5747,7 +5844,8 @@ void RichTextLabel::append_text(const String &p_bbcode) {
 				int height = 0;
 				bool pad = false;
 				String tooltip;
-				bool size_in_percent = false;
+				bool width_in_percent = false;
+				bool height_in_percent = false;
 				if (!bbcode_value.is_empty()) {
 					int sep = bbcode_value.find_char('x');
 					if (sep == -1) {
@@ -5793,7 +5891,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
 					if (width_option) {
 						width = width_option->value.to_int();
 						if (width_option->value.ends_with("%")) {
-							size_in_percent = true;
+							width_in_percent = true;
 						}
 					}
 
@@ -5801,7 +5899,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
 					if (height_option) {
 						height = height_option->value.to_int();
 						if (height_option->value.ends_with("%")) {
-							size_in_percent = true;
+							height_in_percent = true;
 						}
 					}
 
@@ -5816,7 +5914,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
 					}
 				}
 
-				add_image(texture, width, height, color, (InlineAlignment)alignment, region, Variant(), pad, tooltip, size_in_percent, alt_text);
+				add_image(texture, width, height, color, (InlineAlignment)alignment, region, Variant(), pad, tooltip, width_in_percent, height_in_percent, alt_text);
 			}
 
 			pos = end;
@@ -7198,8 +7296,9 @@ 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", "key", "pad", "tooltip", "size_in_percent", "alt_text"), &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), DEFVAL(String()));
-	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("add_hr", "width", "height", "color", "alignment", "width_in_percent", "height_in_percent"), &RichTextLabel::add_hr, DEFVAL(90), DEFVAL(2), DEFVAL(Color(1, 1, 1, 1)), DEFVAL(HORIZONTAL_ALIGNMENT_CENTER), DEFVAL(true), DEFVAL(false));
+	ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region", "key", "pad", "tooltip", "width_in_percent", "height_in_percent", "alt_text"), &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), DEFVAL(false), DEFVAL(String()));
+	ClassDB::bind_method(D_METHOD("update_image", "key", "mask", "image", "width", "height", "color", "inline_align", "region", "pad", "tooltip", "width_in_percent", "height_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), DEFVAL(false));
 	ClassDB::bind_method(D_METHOD("newline"), &RichTextLabel::add_newline);
 	ClassDB::bind_method(D_METHOD("remove_paragraph", "paragraph", "no_invalidate"), &RichTextLabel::remove_paragraph, DEFVAL(false));
 	ClassDB::bind_method(D_METHOD("invalidate_paragraph", "paragraph"), &RichTextLabel::invalidate_paragraph);
@@ -7447,6 +7546,8 @@ void RichTextLabel::_bind_methods() {
 	BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_STYLEBOX, RichTextLabel, progress_bg_style, "background", "ProgressBar");
 	BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_STYLEBOX, RichTextLabel, progress_fg_style, "fill", "ProgressBar");
 
+	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, RichTextLabel, horizontal_rule, "horizontal_rule");
+
 	BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, RichTextLabel, line_separation);
 
 	BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, RichTextLabel, normal_font);

+ 14 - 3
scene/gui/rich_text_label.h

@@ -34,6 +34,7 @@
 #include "core/templates/rid_owner.h"
 #include "scene/gui/popup_menu.h"
 #include "scene/gui/scroll_bar.h"
+#include "scene/resources/image_texture.h"
 #include "scene/resources/text_paragraph.h"
 
 class CharFXTransform;
@@ -143,6 +144,8 @@ protected:
 	void _set_table_column_expand_bind_compat_101482(int p_column, bool p_expand, int p_ratio);
 	void _push_underline_bind_compat_106300();
 	void _push_strikethrough_bind_compat_106300();
+	void _add_image_bind_compat_107347(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, const String &p_alt_text = String());
+	void _update_image_bind_compat_107347(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);
 
 	static void _bind_compatibility_methods();
 #endif
@@ -269,7 +272,8 @@ private:
 		String alt_text;
 		InlineAlignment inline_align = INLINE_ALIGNMENT_CENTER;
 		bool pad = false;
-		bool size_in_percent = false;
+		bool width_in_percent = false;
+		bool height_in_percent = false;
 		Rect2 region;
 		Size2 size;
 		Size2 rq_size;
@@ -281,6 +285,9 @@ private:
 			if (image.is_valid()) {
 				RichTextLabel *owner_rtl = ObjectDB::get_instance<RichTextLabel>(owner);
 				if (owner_rtl) {
+					if (owner_rtl->hr_list.has(rid)) {
+						owner_rtl->hr_list.erase((rid));
+					}
 					image->disconnect_changed(callable_mp(owner_rtl, &RichTextLabel::_texture_changed));
 				}
 			}
@@ -564,6 +571,7 @@ private:
 
 	RID_PtrOwner<Item> items;
 	List<String> tag_stack;
+	HashSet<RID> hr_list;
 
 	String language;
 	TextDirection text_direction = TEXT_DIRECTION_AUTO;
@@ -716,6 +724,8 @@ private:
 		Ref<StyleBox> progress_bg_style;
 		Ref<StyleBox> progress_fg_style;
 
+		Ref<Texture2D> horizontal_rule;
+
 		int line_separation;
 
 		Ref<Font> normal_font;
@@ -762,8 +772,9 @@ public:
 
 	String get_parsed_text() const;
 	void add_text(const String &p_text);
-	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, const String &p_alt_text = String());
-	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_hr(int p_width = 90, int p_height = 2, const Color &p_color = Color(1.0, 1.0, 1.0), HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, bool p_width_in_percent = true, bool p_height_in_percent = false);
+	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_width_in_percent = false, bool p_height_in_percent = false, const String &p_alt_text = String());
+	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_width_in_percent = false, bool p_height_in_percent = false);
 	void add_newline();
 	bool remove_paragraph(int p_paragraph, bool p_no_invalidate = false);
 	bool invalidate_paragraph(int p_paragraph);

+ 6 - 0
scene/theme/default_theme.cpp

@@ -1154,6 +1154,12 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
 	theme->set_stylebox("focus", "RichTextLabel", focus);
 	theme->set_stylebox(CoreStringName(normal), "RichTextLabel", make_empty_stylebox(0, 0, 0, 0));
 
+	Ref<Image> solid_img = Image::create_empty(2, 2, false, Image::FORMAT_RGBA8);
+	solid_img->fill(Color(1, 1, 1, 1));
+	Ref<Texture2D> solid_icon = ImageTexture::create_from_image(solid_img);
+
+	theme->set_icon("horizontal_rule", "RichTextLabel", solid_icon);
+
 	theme->set_font("normal_font", "RichTextLabel", Ref<Font>());
 	theme->set_font("bold_font", "RichTextLabel", bold_font);
 	theme->set_font("italics_font", "RichTextLabel", italics_font);