Why is FlexItem::associatedFlexBox a pointer?

I haven’t seen anything in the FlexBox class that is preventing it from being trivially copyable.

Right now, if you try to create a grid of items and lay them out with nested FlexBoxes, you have to keep all of your Column flexboxes alive until the Row flexboxes go out of scope:

If you do this, you won’t see anything because that inner flexbox goes out of scope:

void Editor::resized()
{
    juce::FlexBox fb;
    
    for( auto i = 0; i < channelComponents.size() / 4; ++i )
    {
        FlexBox column;
        column.flexDirection = juce::FlexBox::Direction::column;
        
        for( auto j = 0; j < channelComponents.size() / 4; ++j )
        {
            column.items.add( juce::FlexItem( *channelComponents[i*4 + j] ).withFlex(1.f) );
        }
        
        fb.items.add( juce::FlexItem(column).withFlex(1.f) );
    }
    
    fb.performLayout(getLocalBounds());
}

Yet, there’s nothing in the FlexBox class that prevents it from being copyable if you’re storing it inside a FlexItem. I haven’t seen a compelling reason to continue keeping this FlexItem member variable as a pointer.

    /** If this is non-null, it represents a FlexBox whose bounds are controlled by this item. */
    FlexBox* associatedFlexBox = nullptr;

All of FlexBox’s member variables are trivially copyable:

class JUCE_API  FlexBox  final
{
public:
//==============================================================================
    Direction flexDirection = Direction::row;
    Wrap flexWrap = Wrap::noWrap;
    AlignContent alignContent = AlignContent::stretch;
    AlignItems alignItems = AlignItems::stretch;
    JustifyContent justifyContent = JustifyContent::flexStart;
    Array<FlexItem> items; //trivially copyable
private:
    JUCE_LEAK_DETECTOR (FlexBox)
};

This is the annoying workaround to make that first example work correctly:

void Editor::resized()
{
    juce::FlexBox fb;
    
    std::vector<juce::FlexBox> columns { static_cast<size_t>(channelComponents.size() / 4) };
    for( auto i = 0; i < channelComponents.size() / 4; ++i )
    {
        auto& column = columns[i];
        column.flexDirection = juce::FlexBox::Direction::column;
        
        for( auto j = 0; j < channelComponents.size() / 4; ++j )
        {
            column.items.add( juce::FlexItem( *channelComponents[i*4 + j] ).withFlex(1.f) );
        }
        
        fb.items.add( juce::FlexItem(column).withFlex(1.f) );
    }
    
    fb.performLayout(getLocalBounds());
}

Is there a reason that FlexBox’s aren’t copyable? Making this change would help a lot of people not make the stupid mistake of letting nested FlexBoxes go out of scope.

Am I missing something obvious that explains why FlexItem::associatedFlexBox should remain as a pointer?

There’s a technical reason: FlexBox contains an array of FlexItem, so needs to see the full definition of FlexItem. If FlexItem held a FlexBox by value, it would need to see the full definition of FlexBox, and we’d have a (slightly worse than the current) circular dependency.

Also, at the moment, FlexItems are very cheap to copy around. Holding a full instance of FlexBox inside each FlexItem would add some amount of overhead which may not be acceptable in practice. From experience, nesting FlexBoxes directly is very rare, and I’d be cautious to slow down the standard use-case just to make an uncommon use-case more convenient.

There are lots of use cases where nesting flex boxes is required. The only workaround I can think of is to make a dummy component whose sole purpose is to lay out a single row or column of child components with a single flex box, and then use multiple instances of that dummy component to form a grid. But that’s lame. Imagine recreating the grid from the MPCs drum pads… that’s a perfect case where you need nested flexboxes. One for each column of buttons, and then one for all of the columns…

Is there a reason that juce::Grid won’t work in that scenario? It sounds like a Grid might be more suitable for that kind of use-case.

In your opinion, is flexbox meant for laying out single axis arrays of objects? I.e. no nesting? Because that is sure not what the web tutorials for using flexbox seem to indicate.

I am more familiar with flexbox instead of grid. That is the only reason I am using flexbox instead of grid.

In projects I’ve worked on, I’ve found that normally FlexBox is more suitable for single-axis layouts, and Grid is more suitable for multi-axis layouts. There have been a few exceptions (sometimes the padding rules of Grid have made it more suitable than FlexBox for single-axis layouts). I can’t think of a situation where I’ve found FlexBox particularly useful for multi-axis layouts - normally if the layout gets that complex, it’s an indicator that I need to split things up so that I have a separate component for each row/column.

1 Like