Bläddra i källkod

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

Pāvels Nadtočajevs 1 månad sedan
förälder
incheckning
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>
 		<link title="Operating System Testing Demo">https://godotengine.org/asset-library/asset/2789</link>
 	</tutorials>
 	</tutorials>
 	<methods>
 	<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">
 		<method name="add_image">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="image" type="Texture2D" />
 			<param index="0" name="image" type="Texture2D" />
@@ -28,15 +42,17 @@
 			<param index="6" name="key" type="Variant" default="null" />
 			<param index="6" name="key" type="Variant" default="null" />
 			<param index="7" name="pad" type="bool" default="false" />
 			<param index="7" name="pad" type="bool" default="false" />
 			<param index="8" name="tooltip" type="String" default="&quot;&quot;" />
 			<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>
 			<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.
 				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] 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.
 				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].
 				[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 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.
 				[param alt_text] is used as the image description for assistive apps.
 			</description>
 			</description>
 		</method>
 		</method>
@@ -655,7 +671,8 @@
 			<param index="7" name="region" type="Rect2" default="Rect2(0, 0, 0, 0)" />
 			<param index="7" name="region" type="Rect2" default="Rect2(0, 0, 0, 0)" />
 			<param index="8" name="pad" type="bool" default="false" />
 			<param index="8" name="pad" type="bool" default="false" />
 			<param index="9" name="tooltip" type="String" default="&quot;&quot;" />
 			<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>
 			<description>
 				Updates the existing images with the key [param key]. Only properties specified by [param mask] bits are updated. See [method add_image].
 				Updates the existing images with the key [param key]. Only properties specified by [param mask] bits are updated. See [method add_image].
 			</description>
 			</description>
@@ -939,6 +956,9 @@
 		<theme_item name="normal_font_size" data_type="font_size" type="int">
 		<theme_item name="normal_font_size" data_type="font_size" type="int">
 			The default text font size.
 			The default text font size.
 		</theme_item>
 		</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">
 		<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.
 			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>
 		</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".
 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.
 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) {
 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) {
 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) {
 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));
 	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() {
 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("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);
 	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("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_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("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
 #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: {
 			case ITEM_IMAGE: {
 				ItemImage *img = static_cast<ItemImage *>(it);
 				ItemImage *img = static_cast<ItemImage *>(it);
 				Size2 img_size = img->size;
 				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);
 					l.text_buf->resize_object(it->rid, img_size, img->inline_align);
 				}
 				}
 			} break;
 			} break;
@@ -591,8 +591,8 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
 			case ITEM_IMAGE: {
 			case ITEM_IMAGE: {
 				ItemImage *img = static_cast<ItemImage *>(it);
 				ItemImage *img = static_cast<ItemImage *>(it);
 				Size2 img_size = img->size;
 				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);
 				l.text_buf->add_object(it->rid, img_size, img->inline_align, 1);
 				txt += String::chr(0xfffc);
 				txt += String::chr(0xfffc);
@@ -2346,6 +2346,21 @@ void RichTextLabel::_notification(int p_what) {
 		case NOTIFICATION_THEME_CHANGED: {
 		case NOTIFICATION_THEME_CHANGED: {
 			_stop_thread();
 			_stop_thread();
 			main->first_invalid_font_line.store(0); // Invalidate all lines.
 			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();
 			_invalidate_accessibility();
 			queue_accessibility_update();
 			queue_accessibility_update();
 			queue_redraw();
 			queue_redraw();
@@ -3952,7 +3967,50 @@ Size2 RichTextLabel::_get_image_size(const Ref<Texture2D> &p_image, int p_width,
 	return ret;
 	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();
 	_stop_thread();
 	MutexLock data_lock(data_mutex);
 	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->rq_size = Size2(p_width, p_height);
 	item->region = p_region;
 	item->region = p_region;
 	item->size = _get_image_size(p_image, p_width, p_height, 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->pad = p_pad;
 	item->key = p_key;
 	item->key = p_key;
 	item->tooltip = p_tooltip;
 	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();
 	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();
 	_stop_thread();
 	MutexLock data_lock(data_mutex);
 	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 (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;
 						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) {
 				if (p_mask & UPDATE_SIZE) {
@@ -5676,6 +5736,43 @@ void RichTextLabel::append_text(const String &p_bbcode) {
 
 
 			pos = end;
 			pos = end;
 			tag_stack.push_front(bbcode_name);
 			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")) {
 		} else if (tag.begins_with("img")) {
 			int alignment = INLINE_ALIGNMENT_CENTER;
 			int alignment = INLINE_ALIGNMENT_CENTER;
 			if (tag.begins_with("img=")) {
 			if (tag.begins_with("img=")) {
@@ -5747,7 +5844,8 @@ void RichTextLabel::append_text(const String &p_bbcode) {
 				int height = 0;
 				int height = 0;
 				bool pad = false;
 				bool pad = false;
 				String tooltip;
 				String tooltip;
-				bool size_in_percent = false;
+				bool width_in_percent = false;
+				bool height_in_percent = false;
 				if (!bbcode_value.is_empty()) {
 				if (!bbcode_value.is_empty()) {
 					int sep = bbcode_value.find_char('x');
 					int sep = bbcode_value.find_char('x');
 					if (sep == -1) {
 					if (sep == -1) {
@@ -5793,7 +5891,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
 					if (width_option) {
 					if (width_option) {
 						width = width_option->value.to_int();
 						width = width_option->value.to_int();
 						if (width_option->value.ends_with("%")) {
 						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) {
 					if (height_option) {
 						height = height_option->value.to_int();
 						height = height_option->value.to_int();
 						if (height_option->value.ends_with("%")) {
 						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;
 			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("get_parsed_text"), &RichTextLabel::get_parsed_text);
 	ClassDB::bind_method(D_METHOD("add_text", "text"), &RichTextLabel::add_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("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("newline"), &RichTextLabel::add_newline);
 	ClassDB::bind_method(D_METHOD("remove_paragraph", "paragraph", "no_invalidate"), &RichTextLabel::remove_paragraph, DEFVAL(false));
 	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);
 	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_bg_style, "background", "ProgressBar");
 	BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_STYLEBOX, RichTextLabel, progress_fg_style, "fill", "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_CONSTANT, RichTextLabel, line_separation);
 
 
 	BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, RichTextLabel, normal_font);
 	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 "core/templates/rid_owner.h"
 #include "scene/gui/popup_menu.h"
 #include "scene/gui/popup_menu.h"
 #include "scene/gui/scroll_bar.h"
 #include "scene/gui/scroll_bar.h"
+#include "scene/resources/image_texture.h"
 #include "scene/resources/text_paragraph.h"
 #include "scene/resources/text_paragraph.h"
 
 
 class CharFXTransform;
 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 _set_table_column_expand_bind_compat_101482(int p_column, bool p_expand, int p_ratio);
 	void _push_underline_bind_compat_106300();
 	void _push_underline_bind_compat_106300();
 	void _push_strikethrough_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();
 	static void _bind_compatibility_methods();
 #endif
 #endif
@@ -269,7 +272,8 @@ private:
 		String alt_text;
 		String alt_text;
 		InlineAlignment inline_align = INLINE_ALIGNMENT_CENTER;
 		InlineAlignment inline_align = INLINE_ALIGNMENT_CENTER;
 		bool pad = false;
 		bool pad = false;
-		bool size_in_percent = false;
+		bool width_in_percent = false;
+		bool height_in_percent = false;
 		Rect2 region;
 		Rect2 region;
 		Size2 size;
 		Size2 size;
 		Size2 rq_size;
 		Size2 rq_size;
@@ -281,6 +285,9 @@ private:
 			if (image.is_valid()) {
 			if (image.is_valid()) {
 				RichTextLabel *owner_rtl = ObjectDB::get_instance<RichTextLabel>(owner);
 				RichTextLabel *owner_rtl = ObjectDB::get_instance<RichTextLabel>(owner);
 				if (owner_rtl) {
 				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));
 					image->disconnect_changed(callable_mp(owner_rtl, &RichTextLabel::_texture_changed));
 				}
 				}
 			}
 			}
@@ -564,6 +571,7 @@ private:
 
 
 	RID_PtrOwner<Item> items;
 	RID_PtrOwner<Item> items;
 	List<String> tag_stack;
 	List<String> tag_stack;
+	HashSet<RID> hr_list;
 
 
 	String language;
 	String language;
 	TextDirection text_direction = TEXT_DIRECTION_AUTO;
 	TextDirection text_direction = TEXT_DIRECTION_AUTO;
@@ -716,6 +724,8 @@ private:
 		Ref<StyleBox> progress_bg_style;
 		Ref<StyleBox> progress_bg_style;
 		Ref<StyleBox> progress_fg_style;
 		Ref<StyleBox> progress_fg_style;
 
 
+		Ref<Texture2D> horizontal_rule;
+
 		int line_separation;
 		int line_separation;
 
 
 		Ref<Font> normal_font;
 		Ref<Font> normal_font;
@@ -762,8 +772,9 @@ public:
 
 
 	String get_parsed_text() const;
 	String get_parsed_text() const;
 	void add_text(const String &p_text);
 	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();
 	void add_newline();
 	bool remove_paragraph(int p_paragraph, bool p_no_invalidate = false);
 	bool remove_paragraph(int p_paragraph, bool p_no_invalidate = false);
 	bool invalidate_paragraph(int p_paragraph);
 	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("focus", "RichTextLabel", focus);
 	theme->set_stylebox(CoreStringName(normal), "RichTextLabel", make_empty_stylebox(0, 0, 0, 0));
 	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("normal_font", "RichTextLabel", Ref<Font>());
 	theme->set_font("bold_font", "RichTextLabel", bold_font);
 	theme->set_font("bold_font", "RichTextLabel", bold_font);
 	theme->set_font("italics_font", "RichTextLabel", italics_font);
 	theme->set_font("italics_font", "RichTextLabel", italics_font);