Why are the FlexBox items disappearing?

I’m running into a strange problem when constructing nested FlexBoxes (a vertical FlexBox containing horizontal FlexBoxes). The code compiles fine and looks like it should work; performLayout calls performLayout on the child FlexBoxes. But for some reason at that point the child FlexBoxes no longer have any items, even though I very clearly added them in the loop. It seems the child hboxes items array is no longer initialized at all. Is this related to the hbox going out of scope? How can I get my desired behavior?

auto rowHeight = bounds.getHeight() / numRows;
for (const auto& row : rows)
{
    auto area = bounds.removeFromTop(rowHeight);
    FlexBox hbox{ FlexBox::Direction::row, FlexBox::Wrap::wrap, FlexBox::AlignContent::center, FlexBox::AlignItems::center, FlexBox::JustifyContent::center };
    for (const auto& control : *row)
    {
        auto& comp = controls[control.id];
        hbox.items.add(FlexItem(*comp).withMinWidth(30));
    }
    vbox.items.add(FlexItem(hbox).withMinHeight(50));
}
vbox.performLayout(bounds);

The docs for the FlexItem constructor taking a FlexBox states:

FlexItem::FlexItem ( FlexBox & flexBoxToControl ) noexcept
Creates an item that represents an embedded FlexBox.
This class will not create a copy of the supplied flex box. You need to ensure that the life-time of flexBoxToControl is longer than the FlexItem.

But hbox goes out of scope, because it is declared in the loop body.

Ok, that’s what I was thinking. What’s the normal way of getting around this?

Edit: This seems to work fine for me

Array<std::unique_ptr<FlexBox>> hboxes;
for (const auto& row : rows)
{
    auto area = bounds.removeFromTop(rowHeight);
    auto hbox = std::make_unique<FlexBox>(FlexBox::Direction::row, FlexBox::Wrap::wrap, FlexBox::AlignContent::center, FlexBox::AlignItems::center, FlexBox::JustifyContent::center);
    for (const auto& control : *row)
    {
        auto& comp = controls[control.id];
        hbox->items.add(FlexItem(*comp).withMinWidth(30));
    }
    vbox.items.add(FlexItem(*hbox).withMinHeight(50));
    hboxes.add(std::move(hbox)); 
}
vbox.performLayout(bounds);

I think that would be the solution, or the juce team would probably resort to OwnedArray. I am not a fan of that, but that’s my personal opinion.

Luckily FlexBox is not polymorphous, so you can put it directly into an juce::Array or std::vector.

I haven’t tested, but this might work:

std::vector<juce::FlexBox> hboxes;
for (const auto& row : rows)
{
    hboxes.emplace_back (FlexBox (FlexBox::Direction::row, 
                                  FlexBox::Wrap::wrap, 
                                  FlexBox::AlignContent::center, 
                                  FlexBox::AlignItems::center, 
                                  FlexBox::JustifyContent::center));
    auto& hbox = hboxes.back();
    for (const auto& control : *row)
    {
        auto& comp = controls[control.id];
        hbox.items.add (FlexItem (*comp).withMinWidth (30));
    }
    vbox.items.add (FlexItem (hbox).withMinHeight (50));
}
vbox.performLayout (bounds);
2 Likes

I realized the second snippet I posted does work (just wasn’t displaying for a separate reason)

I do prefer your way without the extra pointers