HBoxContainer/VBoxContainer: intermediate Control parent nodes have size X/Y = 0, how to auto-fit to children size?

Godot Version

v4.2.1.stable.official [b09f793f5]

Question

Hello, I’m trying to build a HUD with a custom health bar that consists in a series of elements along a HBoxContainer.

However, each element = health cell is quite complex, and consists of 3 elements: frame, BG fill (visible when health segment was lost), cell fill (visible when health segment is filled). So I parent each trio of elements under a common parent.

The easiest seemed to use a Control parent:

image

but it turns out that the Control’s size in the box container direction (here, X for HBoxContainer) is 0, causing all the elements to be placed at the same position.

image

(note the single vertical bar showing that Control parent node size X = 0)

The only ways to force resize the Control parent are:

  • set Custom Minimum Size: good for precise placement, but bad when the children may change size later (due to runtime code or when updating assets), as the value will have to be updated manually

image

  • check Container Sizing > Horizontal > Expand: this will however occupy all the space left inside the container, sharing it with other elements and spreading the controls over the full container, instead of aligning them to the left with minimal size X. I searched for a setting to force alignment along X, but there was none.

image

And an extra solution that doesn’t resize the Control parent but still works if you know the Control size you want in advance, and all the Controls will have the same size:
set HBoxContainer > Theme Overrides > Constants > Separation to the wanted Control size X (more exactly the distance between every Control top-left). This can be enough in simple situations, but you still need to tune the number if you change the UI sprites later.

image

Note the vertical bars: parent controls are still thin, they just happen to be placed at a distance interval equal to the wanted size X.

Besides using Custom Minimum Size, I did find a solution that allows dynamic: replace Control with a Container child class, which happen to fit their own size to their children (more exactly their maximum bounding box). Container base class does nothing on its own so you need to pick a Container child class. Unfortunately there is no “generic” container class that only does this (fit size to children) so we need to pick a Container child class that doesn’t do much.

The easiest is to use one of these:

  • CenterContainer: OK if you want to center your elements (along the orthogonal direction of the Box Container). It will not scale child elements.

image

  • PanelContainer: you must remember to disable the BG with Theme Overrides > Styles > Panel > set to StyleBoxEmpty. It will also scale the element if parent Box Container orthogonal direction is bigger than children should be, so consider setting each child element of Panel Container > Stretch Mode > Keep instead of Scale

Default setup:

image

After hiding panel BG and setting Stretch Mode to Keep on every child of every PanelContainer:

image

  • MarginContainer: make sure margins are 0 (may depend on theme, in this case just set Theme Overrides > Constants > all margins to 0), and you basically get a container whose only role is to fit t children. Like PanelContainer, you may need to set every child Stretch Mode to Keep instead of Scale

(same result as above)

In all these cases, note that you cannot rearrange child element offsets for a custom composition, since containers control element positions. You could add an intermediate Control to allow its own child to set a custom position, but then you’d fall back on the issue of Control size X/Y being 0 (unless you set Custom Minimum Size).

In the examples above, I made my sprites so that they all had the same dimensions, and they would perfectly overlap when placed at the same top-left (this means the inside fill actually contains a padding of 1px on the edges).

The Container child class solution feels a bit hacky and needs more setup on each child.

Is there any way to have the parent Control dynamically fit children easily?

Otherwise I’ll open a feature request to add such an option to the Control node, or alternatively add a more generic Container child class to fulfill this purpose (but we’ll still be unable to tune child offsets…), or add yet another Control child class that is not a Container subclass and is specialized in fitting to child size, e.g. some “ChildFittingControl” node.

By default most Controls will set their Control.size_flags_horizontal and Control.size_flags_vertical to Fill which will take all the space available. If you don’t want that behavior change it to Shrink Begin, Shrink Center, or Shrink End.

Using a PanelContainer or a MarginContainer and setting its Control.size_flags_vertical to Shrink Begin should be enough for what you want to achieve.

Still, if that is not enough and you need your own specialized Container write your own one by extending Container and listening to the Container.sort_children signal. There you’ll be able to setup the position of the children wherever you want.

Indeed, and I think it was when using Shrink Begin that the items start all shrinking back to the same position as the Parent Control is considered of size X = 0.

OK, that should allow me to extend the controller without needing native code. However I’ll have to get inspired from PanelContainer or MarginContainer native code to see how it works to understand the children minimum bounding box and convert that implementation to GDScript (or C#).

In the meantime I opened a proposal discussion for a native container doing the job: Add Control child class/node that fits own size to children (maximum bounding box) to be used as intermediate parent under HBoxContainer/VBoxContainer · godotengine/godot-proposals · Discussion #9396 · GitHub and it would probably do something similar, except in C++.