godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
90.26k stars 21.04k forks source link

RichTextLabel doesn't set its size from the text bbox; uses 0×0 #18260

Closed garyo closed 1 year ago

garyo commented 6 years ago

The regular Label automatically sizes itself based on its text when placed in a container. So for instance three labels in an HBoxContainer will size themselves appropriately horizontally, and set the height of the container to the max height needed to display all the labels. They act as if their min size is the enclosing bbox of the text.

RichTextLabel doesn't do that; if you put three RichTextLabels in an HBox, all three will be invisible because their min size is 0, so they all end up 0x0. Setting Expand size flag doesn't help. This makes it very hard to use RichTextLabel in a responsive design.

This is somewhat related to other RichTextLabel issues: #8049 and #5633, and #10924.

Godot version: Godot 3.1-dev: 7b5703bc4

ghost commented 6 years ago

394e6d5 can't reproduce. Example project is appreciated.

garyo commented 6 years ago

Here's a test project. richtextlabel-test.zip

It has three labels in an HBoxContainer. The first and last are regular labels; they are visible. The middle one is a RichTextLabel; you can't see it because its size is 0x0. To create this, I just created the components in order, didn't change any settings other than the label text. And yes the RichTextLabel does have a text string, so if it behaved the same way as Label, it would have a nonzero size in the HBoxContainer. image

garyo commented 6 years ago

@Noshyaar can you repro the issue with this project?

ghost commented 6 years ago

f20af4b

What I'm able to reproduce: text inside RTL doesn't set its min size.

The way normal Label works is that, when it's not set to autowrap, it sets its min size by the text boundary. So, it'll push the parent container outward and you can't shrink it more. If you turn on autowrap it'll act the same as an RTL.

What I can't: "Setting Expand size flag doesn't help"

Setting Horizontal Expand flag does help for me. It will NOT push the parent container outward. You have to set the parent container size properly. In the project, if you set expand flag and enlarge the parent, it appears.

garyo commented 6 years ago

Well, of course adding Horizontal Expand and enlarging the parent make it work -- but if the text is dynamic I have no idea how much to expand the parent. The general idea of responsive design is the container should adapt to its content. If RTL gets a fix to set its min size based on its content, then that's a good start. However, note that in my test project, if you do set the min size of the HBoxContainer large enough, the RTL does show up but its vertical alignment is weird: image Perhaps that's related to #5633.

harrypujols commented 5 years ago

Because of this issue, I'm unable to use RichTextLabel for dynamic text. Is there a workaround I can grow the RichTextLabel according to the size of the text? This question is unanswered. https://godotengine.org/qa/30459/label-or-richtextlabel-auto-width

pawilon commented 5 years ago

Another image to better visualize how not scaling the size with the text looks in practice:

image

Using RichTextLabel on the left and Label with autowrap on the right, identical container settings. When I type more text on the right, the label and the parent containers resize to fit the text. RichTextLabel doesn't do it.

This makes it hard to use RichTextLabel for dynamic text where a user doesn't know upfront how long the text will be.

jfmajor commented 5 years ago

I dug a big into this since I need a way to resize my RichTextLabels according to the size of the text. I noticed if I set the fixed_width(not currently exposed in gdscript or editor) to something other than -1, the RTL's size in Y get's properly updated like @pawilon 's example(on the right). I'm guessing get_minimum_size() returning get_content_height() being why it works. Not quite sure why fixed_width even exists. Shouldn't it use min_size instead?

jfmajor commented 5 years ago

Made a fix on our end. Not 100% happy with it. I think it should involve removing fixed_width which appears to be a hack. The scrollbars being the behavior I'm not sure how to work around in an elegant way. I understand we might not want the text to grow the control's size automatically if we do want scrollbars. For now, you can view our changelist. Borrows from fixed_width but will use the minimum_size instead. https://github.com/jfmajor/godot/commit/eed621588351c0caed157b198ae4849aea54367f

mjtorn commented 5 years ago

Wouldn't fixed width exist exactly for the reason that you don't, under any circumstances, want to resize anything?

RichTextLabel does try a bit[1] to resize itself when setting a size smaller than its content width, so I'd suppose having a fixed width would alter this behavior.

I look forward to trying your patch as soon as possible, these kinds of problems are the worst when trying to use Godot :)

[1] Doesn't work at least on Mac and 3.0.6, I'll probably open an issue next year, if I first try some different fonts and whatnot.

jfmajor commented 5 years ago

@mjtorn the patch adds a Auto Resize To Text option you need to check. Did you set it?

mjtorn commented 5 years ago

@jfmajor I haven't had time yet... I have a lot of accounting and reporting to do between xmas and new year, so it might be until early January.

But setting the width of something, eg. like we do with tooltips in Escoria since godotengine/escoria@09d710182c90bd8a56d54fd1e7f063acf36b381b, mostly works. I wish I had tested more on Mac before writing that commit message ;)

Beside that it's that we don't have VAlign. Content margins with an auto-growing RTL (which your commit seems to address) might be a better solution than simply aligning vertically. Let's see.

This might be going off-topic, but I'd attach this work-in-progress screenshot of our game with the German localization in any issue I'd create.

iscw-demo-kugelschreiber-hans
girng commented 5 years ago

hope this can make it in soon. this would help so much for creating dynamic tooltips that fit their bbcode size appropriately. you can do a trick where you have a rogue label and set its modulate.a=0, then set the RTL to the label's rect_size. however, that works great for short bbcode. problem is, if you do [color=red]mytext[/color] and other bbcode tags, the label's rect will be very long, and then the RTL's width will become very long as well. actually.. you could go even further and just strip the bbcode tags in the label's text via regex, and you would get a more accurate size

or, the other partial solution is to just set a fixed width, and use the height from the rogue label. but then, you have to use a fixed width. if there were a setting so it works just like a label, that would be ideal imo

@jfmajor i'm going to compile and use your code. i'll re-edit this when i'm back
edit: @jfmajor i found an issue. when it resizes, it increases the height by too much, example gif. it also doesn't expand the width based on the letters like a label would. am not sure

mjtorn commented 5 years ago

@jfmajor sorry that I haven't had the opportunity to try your code yet, I'm stuck doing billable work and such for a while now :( But is it good enough to become a PR into Godot? If it works, I'd use it anyway, but now I need to look after other things first.

jupiterjosh commented 5 years ago

This is biting me too :-( I will look at source after I get my game out the door and money (if any :-p ) coming in.

girng commented 5 years ago

@jupiterjosh would love this feature =]

basically, to reduce this issue/request into a one liner:

jupiterjosh commented 5 years ago

@girng please don't paraphrase someone else. If you want to quote someone or respond to someone then great, but speaking for someone isn't appropriate.

girng commented 5 years ago

@jupiterjosh i'm speaking for someone? who?

hedin-hiervard commented 5 years ago

My 5c: I'm using get_minimum_size() method on Labels to detemine their real size right after I programmatically set the string. It's the only way to rearrange controls in the same block without waiting for next frames etc.

This doesn't work for RichTextLabel, I think the reason is the same - the lack of proper size computation.

hedin-hiervard commented 5 years ago

Any updates on the issue?

smark91 commented 5 years ago

Hi there! Any news?

RaaliOloth8 commented 5 years ago

Hello, waiting for proper size computation too

yvesns commented 5 years ago

Possible workaround:

size_flags_horizontal = SIZE_EXPAND_FILL scroll_following = true yield(get_tree(), "idle_frame") rect_min_size.y = get_v_scroll().value

hedin-hiervard commented 5 years ago

Possible workaround:

size_flags_horizontal = SIZE_EXPAND_FILL scroll_following = true yield(get_tree(), "idle_frame") rect_min_size.y = get_v_scroll().value

It doesn't work for me (it sets some arbitrary large values far more than the actual height).

sszigeti commented 5 years ago

Using Godot 3.1.1.stable.official this is a tolerable hack for vertically resizing a RichTextLabel and its parent (which is a PanelContainer in my case).

onready var container:PanelContainer = $PanelContainer
onready var label:RichTextLabel = $PanelContainer/RichTextLabel
onready var panel:Panel = container.get("custom_styles/panel") # for calculating the proper y padding
var recalc_height_required := false

func _ready() -> void:
    label.size_flags_vertical = Control.SIZE_EXPAND_FILL
    label.scroll_active = false # to prevent the scrollbar from briefly appearing (due to the idle_frame yield)
    label.scroll_following = false # to avoid a long content to jump as it's waiting for the label height being adjusted

func _process(_delta:float) -> void:
    if recalc_height_required:
        recalc_height_required  = false
        _recalc_height()

func _update_label(markup:String) -> void:
    label.bbcode_text = markup
    yield(get_tree(), "idle_frame")
    _recalc_height()
    recalc_height_required = true

func _recalc_height() -> void:
    label.rect_size.y = label.get_v_scroll().get_max()
    container.rect_size.y = label.rect_size.y
    container.rect_size.y += panel.content_margin_top
    container.rect_size.y += panel.content_margin_bottom
hedin-hiervard commented 5 years ago

Using Godot 3.1.1.stable.official this is a tolerable hack for vertically resizing a RichTextLabel and its parent (which is a PanelContainer in my case).

onready var container:PanelContainer = $PanelContainer
onready var label:RichTextLabel = $PanelContainer/RichTextLabel
onready var panel:Panel = container.get("custom_styles/panel") # for calculating the proper y padding
var recalc_height_required := false

func _ready() -> void:
    label.size_flags_vertical = Control.SIZE_EXPAND_FILL
    label.scroll_active = false # to prevent the scrollbar from briefly appearing (due to the idle_frame yield)
    label.scroll_following = false # to avoid a long content to jump as it's waiting for the label height being adjusted

func _process(_delta:float) -> void:
    if recalc_height_required:
        recalc_height_required  = false
        _recalc_height()

func _update_label(markup:String) -> void:
    label.bbcode_text = markup
    yield(get_tree(), "idle_frame")
    _recalc_height()
    recalc_height_required = true

func _recalc_height() -> void:
    label.rect_size.y = label.get_v_scroll().get_max()
    container.rect_size.y = label.rect_size.y
    container.rect_size.y += panel.content_margin_top
    container.rect_size.y += panel.content_margin_bottom

This doesn't work as well.

Favorlock commented 5 years ago

It'd be nice if this would be fixed soon. As it stands RichTextLabel is practically useless due to the fact that it doesn't size itself along the y axis. The only way to get it to show is to expand vertically, but that is undesirable due to the fact that it leads to excess space.

rslepon commented 4 years ago

Just adding my "me too". At the moment, the only solution I have found is to avoid using RichTextLabel altogether. Which is a sad solution.

baseballpunt commented 4 years ago

Had me scratching my head for a while, was projecting RichTexts onto a canvas in 3D, couldn't figure out why the RichText wouldn't show but normal labels would. Found this, setting a manual minsize fixed, will just have to play around with numbers for each piece of text I suppose. Hope to see this fixed.

puthre commented 4 years ago

Having the same problem when trying to get the RichTextLabel dimensions for a dynamic text in order to align the text in the center of a text bubble.

Having a quick look at the implementation it seems that RichTextLabel is not very aware if it's minimum required width and you can only get the content height (with get_content_height function).

Pilvinen commented 4 years ago

Still very much a problem.

I posted my solution, written in C#, here (waiting for approval): https://godotengine.org/qa/30459/label-or-richtextlabel-auto-width

Well, it's taking it's sweet time so here's a gist with the same code: https://gist.github.com/c3a858e2fabe87f3ead967dd15a25d98

You're welcome.

akien-mga commented 4 years ago

This issue is now worked around by the new fit_content_height option in RichTextLabel (in 3.2.2 and 4.0).

But a different fix will likely end up being implemented for a more though "width-for-height" adjustment system for containers as seen e.g. in GTK.

lucassardois commented 3 years ago

This issue is still revelent in Godot 3.2.3 Stable. With the following setup:

HBoxContainer
-- Label
-- RichTextLabel
-- Label

The RichTextLabel have a min size of 0x0 even when there is text inside. The option fit_content_height doesn't solve the width problem. Also, setting the horizontal or vertical size_flag does nothing.

true2thepen commented 3 years ago

My solution:

Place the following on the RichTextLabel's parent.

extends [Parent Type]

func _ready():
    self.connect("resized", self, "_on_resize")

func _on_resize():
    var myChild = self.get_child(0) # Change this to find correct child if RichTextLabel isn't the only child
    myChild.set_size((self.rect_size)) # Change to fit if RichTextLabel isn't only child.

Works best if RichTextLabel is only child. Has jitter during resize; but,.... at least the text no longer runs outside. This is by no means perfect but works with Godot 3.2.3 Stable.

db0 commented 3 years ago

I extended the RichTextLabel with this code to make this work more seamlessly.

func get_min_height() -> float:
    var theme : Theme = self.theme
    var label_font : Font
    if theme:
        label_font = theme.get_font("font", "RichTextLabel").duplicate()
    else:
        label_font = get("custom_fonts/normal_font").duplicate()
    return(label_font.get_height())

Then I extended set_bbcode():

func set_bbcode(value):
    .set_bbcode(value)
    rect_min_size.y = get_min_height()
db0 commented 3 years ago

Edit, d'oh, I just found out fit_content_height exists defaulted to false. Setting this to true makes this work without hacks o.O

EpeonGamer commented 3 years ago

Do we have a stuatus on anything for width?

nathanfranke commented 3 years ago

You can get creative with Font.get_string_size and Font.get_wordwrap_string_size.

If you have a single line of text always, you can just use get_string_size by itself. If you want to have multiple lines with automatic width adjustment, you can use this code:

extends RichTextLabel

# TODO: Only run this code when the text is modified
func _process(_delta: float) -> void:
    rect_size = get_font("normal_font").get_wordwrap_string_size(text, INF)
    rect_size.x = 0.0
    for line in text.split("\n"):
        rect_size.x = max(rect_size.x, get_font("normal_font").get_string_size(line).x)

Note that this doesn't work with multiple sized fonts (e.g. bigger bold text).

example

Calinou commented 3 years ago

Note that this doesn't work with multiple sized fonts (e.g. bigger bold text).

If you absolutely need this, you can use an uniwidth (≠ fixed-width) font such as Recursive to work around this.

KoBeWi commented 2 years ago

But a different fix will likely end up being implemented for a more though "width-for-height" adjustment system for containers as seen e.g. in GTK.

This isn't really needed. We need 2 things: option for RichTextLabel to expand to its maximum size in a direction (horizontally or vertically) and a maximum size setting. If you want a RTL to just take full width, set the expand to horizontal and leave maximum size at (0, 0), which would disable it. If you want to fit it in a somewhat fixed message box, set maximum size to the desired value, so that RTL will expand to that size and then wrap the text. The minimum size in the other axis would be easy to calculate.

lpares12 commented 1 year ago

Is this gonna be implemented for Godot 4?

@KoBeWi do you have any work around for when the container should take the size of the RTL, even if it's smaller than the size set in the container? For example in the image posted by @pawilon:

48555446-8ca96f00-e8e1-11e8-8ca5-c58014eab91c

What if the text in the container was only "hello", then the container would take the manually set width, instead of fitting the word "hello"

Calinou commented 1 year ago

Is this gonna be implemented for Godot 4?

Only if a motivated contributor steps up to do the required work :slightly_smiling_face:

I don't know if @bruvzg has looked into this in the past, but it's not an easy problem to resolve. Handling fonts with different sizes in particular makes this more challenging.

KoBeWi commented 1 year ago

do you have any work around for when the container should take the size of the RTL, even if it's smaller than the size set in the container? For example in the image posted by

I don't understand that image. The container does correctly resize to RIchTextLabel. I just tried the same setup and I don't have this problem. It might be some issue that was fixed since then.

lpares12 commented 1 year ago

The issue is that RTL resizes according to the height, when the fit content height is set. But there's nothing equivalent for the width, meaning the max width is always set by the parent width, if that makes sense. With normal labels it is possible to write text and the parent container will expand horizontally according to it.

abelbascu commented 2 months ago

I'm working on a visual novel and after much search I found this thread. I have a RichtextLabel inside a TextureButton. This combo is a player dialogue choice. There can be multiple dialogue choices inside a containerUI (like in Monkey Island games). The texts overlap, specially when the text autowraps to a second text line... it's like that after the autowrap, there is not any autoresize occurring on the parent node (TextureButton), so I guess I will need to check the height size of the RichTextLabel and notify that to the TextureButton to change its height on a method, so the containerUI that has all the player choices can automatically grow upwards to prevent the texts overlapping... Is there any easier solution? is there any advancement on this issue? Thanks in advance.

KoBeWi commented 2 months ago

You could put TextureButton as a child of RichTextLabel, set anchors to Full Rect and enable draw_behind_parent. It should look more or less the same, but size automatically.

Or I guess you could put both in a MarginContainer as siblings. The container will fit to the biggest child.