3

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

Jay
  • 19,649
  • 38
  • 121
  • 184
  • The short answer is no. However, such custom container can be created (by extending the `Container` class), and there are a few addons already existing. But I'd need extra information to decide if one of those would work for you (or to explain how to implement it): Do you need a container that can change between vertical an horizontal? Or do you need a container that will fill a row and the add a new one (or fill a column and then add a new one)? Any other extra parameters (e.g. you need to set the maximum number of elements, rows or columns, or the alignment of the elements, etc)? – Theraot Mar 31 '22 at 10:24
  • Thanks @Theraot - I am still new to this – In this particular situation, I don't need anything fancy. The closest analogy I can imagine is text in a text editor. Each button has variable width text, and extra buttons move to the start of the next line. (But now that you say it I can see how this type of thing more generalized would solve for other situations where you need to rotate the screen on a mobile device!) – Jay Apr 01 '22 at 01:09
  • 1
    Godot will handle the orientation by default, instead you get a resize. Thus, usually you can pretend there is no change of orientation and treat it like a resized window. So the need to change the orientation of the container is rare, even considering mobile devices. By the way, if you need to find out the orientation you can use [`OS.screen_orientation`](https://docs.godotengine.org/en/stable/classes/class_os.html?highlight=OS#class-os-property-screen-orientation). Edit: I'll also let you know that you can use index access on vectors, so `vector[0]` is the same as `vector.x` and so on. – Theraot Apr 01 '22 at 08:02

1 Answers1

6

Flow container for Godot 3.5 or newer

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.


Flow containers before Godot 3.5

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.


Making your own custom 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 Controls 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 Controls. When there is room for the Controls 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 Containers - you will need to iterate over the children Controls twice. And for that you might find useful to have an auxiliary data structure to temporarily store them.

Theraot
  • 31,890
  • 5
  • 57
  • 86
  • Amazing, thank you. I think we'll update to the beta. We are not launching for a month or so, so this should work. Thanks! – Jay Apr 01 '22 at 10:23
  • 1
    @Jay There will probably be a couple release candidates before the stable, so it might take longer than a month. Make sure to test everything (and please report any problem you find). If it all works on the beta, you might even release on the beta. *I've done it before, shhh.* It matters more your game works correctly than Godot being on an stable version. – Theraot Apr 01 '22 at 10:37