Expand RichTextLabel in both directions

Godot Version

4.4.1

Question

Is it possible to arrange nodes in such a way that a RichTextLabel would expand both horizontally and vertically? Essentially, I’d like to create chat bubbles that match the text length horizontally when they have just a single line, but then expand vertically when they have multiple lines.

I could definitely do it by code, but I’d prefer to use the correct container nodes to achieve this if it is possible.

You’ll need to set RichTextLabel.autowrap_mode to Off so it does not autowrap and grow itself horizontally and enable RichTextLabel.fit_content so it grows vertically.

Then you’ll need to set the RichTextLabel or its container Anchors preset under Layout to Custom and manually configure the Anchor Points and Anchor Offsets, and set its Grow Direction Vertical and Horizontal to Both

2 Likes

With this setup, I can make the RichTextLabel expand horizontally, but I cannot make it grow vertically, no matter what I do. Can you give specific examples of the Anchor Points and Anchor Offsets? Or a screenshot of the setup.

Changing the Fit Content and Autowrap Mode values I can only ever make it either expand horizontally or vertically, not both with a set max width.

It’s not possible to give a max width to any Control node. The RichTextLabel should expand vertically in both directions with each new line.

2 Likes

I can replicate that behaviour. However, what I wanted is to have the same behaviour without forced line breaks. First, grow horizontally until a specific width is reached, then grow vertically with automatic line breaks. Is that possible with just nodes and their parameters or do I have to add forced line breaks in code to achieve this?

If not, the easiest way to achieve this is probably inserting forced line breaks via code. Just a bit messy as not all characters are the same width so it’s not super simple to calculate where to accurately place those line breaks.

It’s only possible by adding line breaks. As long as you don’t use BBCode then it’s possible to add them automatically by per-calculating them before showing the text on screen.

Example:

extends Node

@onready var panel_container: PanelContainer = $PanelContainer
@onready var rich_text_label: RichTextLabel = $PanelContainer/RichTextLabel

func _ready() -> void:
	await get_tree().create_timer(5).timeout
	await message("""Donec felis augue, bibendum fermentum lectus in, sagittis commodo risus. Aliquam vestibulum mauris vitae rhoncus fermentum. Aliquam pulvinar posuere nulla. Ut vel imperdiet orci. Aliquam ac leo pretium, auctor dui et, fringilla ex. Fusce id volutpat velit, nec tincidunt quam. Suspendisse in mattis justo. Nullam venenatis at nisl in venenatis. Cras eleifend congue dictum. Vestibulum pharetra laoreet sapien sit amet luctus. Phasellus viverra enim sit amet imperdiet rhoncus. Aenean eget dolor a mi porttitor efficitur eu sit amet libero.""")
	await get_tree().create_timer(2).timeout
	await message("""Fusce vitae ornare felis. Phasellus mattis pulvinar commodo. In hac habitasse platea dictumst. Suspendisse auctor id mi a aliquet. Quisque ut tortor maximus, eleifend sapien a, semper nibh. In libero libero, cursus in nisi ut, fringilla bibendum purus. In semper quam eget purus sodales euismod. Vivamus hendrerit, ipsum vel tempus ultricies, turpis dui tempus dolor, eget accumsan mi erat et dui. Sed turpis metus, pretium ut nulla eu, lobortis varius purus. Nulla vehicula ex vitae libero aliquet elementum. Nam tempor dui scelerisque sollicitudin dapibus.""")

func message(text:String) -> void:
	# Pre-calculate the final text lines
	panel_container.modulate.a = 0
	panel_container.custom_minimum_size.x = 500
	rich_text_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
	rich_text_label.text = text
	# Await a couple frames so everything is correctly setup
	await get_tree().process_frame
	await get_tree().process_frame
	# Get the lines and create the new text by appending a line break
	var new_text = ""
	for line in rich_text_label.get_line_count():
		var range = rich_text_label.get_line_range(line)
		new_text += text.substr(range.x, range.y-range.x) + "\n"

	# Disable autowrap and set the new text
	rich_text_label.autowrap_mode = TextServer.AUTOWRAP_OFF
	rich_text_label.text = new_text

	# Set the visible ration to 0
	rich_text_label.visible_ratio = 0.0
	# Position and reset the size of the panel container
	var vp_size = get_viewport().size
	panel_container.position = vp_size / 2.0
	panel_container.grow_horizontal = Control.GROW_DIRECTION_BOTH
	panel_container.grow_vertical = Control.GROW_DIRECTION_BOTH
	panel_container.custom_minimum_size = Vector2.ZERO
	panel_container.size = Vector2.ZERO

	# Tween the visible ratio
	var tween = create_tween()
	tween.tween_property(panel_container, ^"modulate:a", 1.0, 0.15)
	tween.parallel().tween_property(rich_text_label, ^"visible_ratio", 1.0, 5.0)
	await tween.finished

If you are using bbcode then it’s not possible to add them easily via code.

2 Likes

I hear you and I raise you my solution without adding line breaks :slight_smile:
You can ignore the _type_letter() function later, this is just for filling in the label.

extends RichTextLabel


const MAX_WIDTH: float = 500.0


func _ready() -> void:
	resized.connect(_on_resized)
	$"../Timer".timeout.connect(_type_letter)
	text = ""


func _type_letter() -> void:
	text += char(randi_range(97, 123))
	text += " " if randf() > 0.7 else ""


func _on_resized() -> void:
	if size.x > MAX_WIDTH:
		autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
		size.x = MAX_WIDTH

2 Likes

Thank you so much for the samples! Wchc, do you think your code could support bbcode styling too?

Yes, I added some sloppy code to add some random BBCode tags and this is the result

2 Likes

Great! I just added some code to support shrinking the label as well, and now it works great. Thank you to both of you! :slightly_smiling_face:

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.