Long discussion regarding rect_max_size-property with an open proposal linked
In CSS this would be easy:
display: inline-block; /* Make element as wide as content ... */
min-width: 100px; /* ... but at least 100px wide ... */
max-width: 500px; /* ... and at maximum 500px wide */
Question
Is there an easy way to do this that I am missing?
(Or a complicated recommendation?)
There’s not easy way. The RichTextLabel fits its content to the given size. If you give it 100 pixels width it will do its best to fit its content to 100 pixel width. if you give it 500 pixels it will do the same.
What you ask is still possible but it’s not a simple thing.
extends RichTextLabel
@export var max_width:float = 500.0
var strings = ["hi", "hello!", "Hello there handsome!", "Lorem [b]ipsum[/b] dolor sit amet, [font_size=16]consectetur adipiscing elit.[/font_size] Ut faucibus consectetur sapien sed malesuada. Sed pellentesque tempus consequat. Aliquam ac facilisis sem. In convallis, quam quis interdum eleifend, lectus nibh mattis enim, ac iaculis sapien magna in ligula. Praesent sit amet aliquet erat, ac tempor sem. Sed sollicitudin enim vitae sem aliquam, a semper ante sodales. Vestibulum luctus venenatis velit in auctor. Nunc luctus mollis lacus. Morbi viverra congue diam, nec dapibus velit elementum at. In accumsan hendrerit diam. Integer feugiat laoreet cursus. Phasellus laoreet ipsum a laoreet viverra. Aliquam posuere enim dolor, sit amet pulvinar diam gravida ac. Maecenas cursus ultrices nunc eu semper."]
func _ready() -> void:
size = Vector2.ZERO
fit_content = true
finished.connect(_fit_width, CONNECT_DEFERRED)
text = strings[0]
await get_tree().create_timer(3).timeout
text = strings[1]
await get_tree().create_timer(3).timeout
text = strings[2]
await get_tree().create_timer(3).timeout
text = strings[3]
func _fit_width() -> void:
# block the signals so "finished" does not trigger this function again
set_block_signals(true)
var original_autowrap = autowrap_mode
# save the position
var tmp = global_position
# move it out of the way to avoid flashing
global_position.x = -100000
# disable autowrap
autowrap_mode = TextServer.AUTOWRAP_OFF
# make it 0, 0
size = Vector2.ZERO
# wait one frame
await get_tree().process_frame
# now we have the size with no autowrap
# if the width is bigger than max width clamp it
var w = clampf(size.x, 0, max_width)
var h = size.y
# restore the autowrap mode
autowrap_mode = original_autowrap
# set the maximum size we got
size.x = w
# wait one frame for the text to resize
await get_tree().process_frame
# if the height is bigger than before we have multiple lines
# and we may need to make the width smaller
if size.y > h:
# save the height
h = size.y
# keep lowering the width until the height changes
while true:
# lower the width a bit
size.x -= 10
# wait one frame
await get_tree().process_frame
# check if the height changed
if not is_equal_approx(size.y, h):
# if it changed we made the textbox too small
# restore the width and break the while loop
size.x += 10
break
# wait one frame
await get_tree().process_frame
# restore the height
size.y = h
# restore the original position
global_position = tmp
# unblock the signals
set_block_signals(false)
Result:
I did it in the same RichTextLabel node to quickly test it. Because it takes a few frames to get the final size, it would be better if you use an off-screen RichTextLabel to get the final size and then copy that information to the one you are showing