Think of an RPG game where you might need to present a list of buttons. A user might enter a room where they have to select from a series of options (buttons). Is there a type of container/panel that would show clickable buttons horizontally, but wrap if needed?
The best analogy I can think of to picture the situation is, Imagine needing to click on an item in a backpack, but each item is potentially a different width. (I can make them all the same height but the width then varies)
.---[My Backpack]------.
| aaa bbb cccc ddd |
| ee fff g |
| |
| |
`----------------------'
(The options come from a database, so its unknown at compile time how many options might be in a room, so I am going to need to programmatically add options.)
The very bottom of this godot document introduces custom container layouts, but it's unclear to me how this would work in practice
Godot 3.5 (currently in beta) introduces HFlowContainer
and VFlowContainer
that will serve the propuse described.
The HFlowContainer
will fill a row and when they overflow, it will add a new row and continue there. The VFlowContainer
will work on a similar fashion but with columns.
For older versions of Godot you can use the HFlowContainer
addon which you can find it in the asset library (here). Note that there is no VFlowContainer
counterpart.
As everything on the asset library it is free and open source, so feel free to read the code and modify it, which can be serve as starting point if you want to make your own custom Container
.
Container
The gist of making a custom Container
is that it must position its children.
For that effect you react to NOTIFICATION_SORT_CHILDREN
in the _notification
method. You might also want to react to NOTIFICATION_RESIZED
.
You can have a method - which I'll call layout
- that you call when you get the notifications:
func _notification(what):
if what == NOTIFICATION_SORT_CHILDREN:
layout()
And also call layout
from the setters (setget
) of the properties that define how the Container
must organize its children. To call layout
from anywhere other than _notification
, you might want to use call_deferred("layout")
to prevent any possible re-layout loops from hanging or crashing the game.
The layout
method would iterate over the visible children Control
s and use get_combined_minimum_size
to figure out their size.
Something like this:
func layout() -> void:
# …
for child in get_children():
var control := child as Control
if not is_instance_valid(control) or not control.visible:
continue
var size := control.get_combined_minimum_size()
# …
Then using that information compute the position and size for the children Control
s. When there is room for the Control
s to grow, you may want to split it among them according to their size_flags_stretch_ratio
.
Once you have the position and size for a Control
decided, use fit_child_in_rect
to position them, which will take into account grow and size flags.
Thus - barring the simplest Container
s - you will need to iterate over the children Control
s twice. And for that you might find useful to have an auxiliary data structure to temporarily store them.