Nested FlexBox items?


#1

I haven’t seen this asked or answered. Is it possible to nest FlexBoxes inside of Flexboxes? I saw this constructor for FlexItem: FlexItem (FlexBox& flexBoxToControl) noexcept; but I haven’t been able to get it to work properly.

The sequence I’m using is:

  1. create Component that needs positioning and make it visible

  2. Add component to FlexItem:

  3. Add FlexItem to FlexBox subInterface

  4. Add FlexBox to FlexItem mainInterfaceItem( subInterface)

  5. add mainInterfaceItem to FlexBox:

  6. in resized(), call mainInterface.performLayout(getLocalBounds().reduced(5));
    however, nothing shows up.

    private:
    ScopedPointer elem;
    FlexBox subInterface;
    FlexBox mainInterface;

addAndMakeVisible( elem = new PlaceholderComponent("place holder") );
subInterface.items.add( *elem );
mainInterface.items.add(subInterface.withFlex(0.2));

I’m simplifying it a bit for the post, but is that the right usage if I want to do some flexBox nesting?

Here’s a nice visual example I found showing what i’m talking about:


#2

figured it out!

    elements.add(new PlaceHolder("content box", false ) );
    auto& content = *(elements.getLast());
    addAndMakeVisible( content );
    contentBox.items.add(FlexItem( content ).withFlex(1.0));
    FlexItem contentItem(contentBox);
    interface.items.add(contentItem.withFlex(2.0).withMargin({5.0, 0.0, 5.0, 0.0}));

#3

ok, for the life of me, I cannot figure out why this crash is happening when I select ‘9’ from the comboBox:

header file:

struct PlaceHolder : public Component {
PlaceHolder(const String& _title, bool _gap) : Component(_title) { title = _title; gap = _gap; }
    ~PlaceHolder() {}
    void paint( Graphics& g ) override {
        if( !gap ) {
            g.setColour(juce::Colour(24, 120, 41));
            g.fillRoundedRectangle(getLocalBounds().toFloat(), 5);
        } 
        g.setColour(Colours::black );
        g.drawText(title, getLocalBounds(), juce::Justification::centred);
    }
    void mouseDown(const juce::MouseEvent &event) override{
        DBG( "Width: " + String(getWidth()) );
    }
    String title;
    bool gap;
};
class FlexView    : public Component,
public ComboBox::Listener
{
public:
    FlexView();
    ~FlexView() {}

    void paint (Graphics&) override;
    void resized() override;
    void comboBoxChanged(juce::ComboBox *comboBoxThatHasChanged) override;

private:
    int loadedSession = 0;
    ComboBox allUsersList;
    OwnedArray<Component> elements;
};

implementation:

FlexView::FlexView() {
    allUsersList.addListener(this);
    for( int i = 1; i < 10; ++i) {
        allUsersList.addItem(String(i), i);
    }
    allUsersList.setSelectedItemIndex(0);
}

void FlexView::comboBoxChanged(juce::ComboBox *comboBoxThatHasChanged) {
    if( comboBoxThatHasChanged == &allUsersList ) {
        DBG( "comboBoxChanged selectedID:" + String(allUsersList.getSelectedId()) );
        loadedSession = allUsersList.getSelectedItemIndex();
        allUsersList.setSelectedItemIndex(loadedSession);
        resized();
    }
}
void FlexView::resized() {
    elements.clear();
    FlexBox headerBox;
    {
        headerBox.items.add(FlexItem(allUsersList).withFlex(1.0).withMargin({2.0}) );
        addAndMakeVisible(allUsersList);
    }
    FlexBox contentBox;
    {
        FlexBox segmentBox;
        segmentBox.flexWrap = FlexBox::Wrap::wrap;
        segmentBox.flexDirection = FlexBox::Direction::row;
        {
            auto user = allUsersList.getSelectedItemIndex() + 1;
            DBG( "count: " + String( user ) );
            for( int i = 0; i < user; ++i ) {
                segmentBox.items.add( FlexItem(1, 1).withFlex(1).withMargin({2.0}).withMinWidth(80) );
                auto& flexItem = segmentBox.items.getReference( segmentBox.items.size() - 1);
                elements.add( new PlaceHolder("segmentBox" + String(i+1), false) );

                addAndMakeVisible( elements[elements.size()-1] );
                flexItem.associatedComponent = elements[elements.size()-1];

            }
        }
        contentBox.items.add( FlexItem(segmentBox).withFlex(1.0).withMargin({2.0}) );
    }
    FlexBox selectedSequenceBox;
    {
        elements.add( new PlaceHolder("selectedSequenceBox", false) );
        addAndMakeVisible( *(elements.getLast()) );
        selectedSequenceBox.items.add( FlexItem( *(elements.getLast()) ).withFlex(1.0).withMargin({2.0}) );
    }
    FlexBox footerBox;
    {
        elements.add( new PlaceHolder("footerBox", false) );
        addAndMakeVisible( *(elements.getLast()) );
        footerBox.items.add( FlexItem( *(elements.getLast()) ).withFlex(1.0).withMargin({2.0}) );
    }
    FlexBox interface;

    interface.flexDirection = FlexBox::Direction::column;
    interface.items.add( FlexItem(headerBox).withFlex(0.5) );
    interface.items.add( FlexItem(contentBox).withFlex(1) );
    interface.items.add( FlexItem(selectedSequenceBox).withFlex(0.5) );
    interface.items.add( FlexItem(footerBox).withFlex(0.5) );

    interface.performLayout(getLocalBounds());
}

I don’t know why the segmentBox nested code is causing problems. here’s what it looks like when you select something between 2 and 8:

As you can see, item 1 is never drawn, which is just odd. When I select 9 from the combobox, I get a crash in the juce::FlexBoxLayoutCalculation::alignItemsByJustifyContent(), being given an ‘EXC_BAD_ACCESS’ crash.


#4

would you mind to upload stripped down files so i can just drop them down into solution ?


#5

tried to do it myself but there are many surprises on the go.
anyway, i dont think that setting it all up in resized() method is the best idea to consider. it will be called several times during the startup and perhaps under some other activity. there are many crucial thing happening in conjunction to the state of your program. no ?


#6

Creating and destroying all your child components in resized() isn’t going to work, and presumably somewhere you’re leaving a dangling pointer to a child component hanging around which a flexbox object is attempting to position.


#7

Sure thing, here ya go @do.while

MainComponent.cpp (3.1 KB)
MainComponent.h (2.0 KB)

regarding doing all the work in resized(), I was just following what was shown here: FlexItem with dynamic width


#8

No, you’re misunderstanding that. Sure, create your FlexBox objects in resized, that’s trivial, but you shouldn’t mess around creating/adding/removing child components in there.


#9

ok. based on my debug statements, resized() was only being called twice. Once when the app loads, and then once again when setSelectedId() the comboBox in the MCC constructor. If i didn’t have resized() inside the comboBoxChanged() method, resized() would only be called once.


#10

ok, I modified things so construction happens in the comboBoxChanged() method:

void MainContentComponent::comboBoxChanged(juce::ComboBox *comboBoxThatHasChanged) {
    if( comboBoxThatHasChanged == &allUsersList ) {
        DBG( "comboBoxChanged selectedID:" + String(allUsersList.getSelectedItemIndex()) );
        loadedSession = allUsersList.getSelectedItemIndex() + 1;
        elements.clear(); //this is OwnedArray<Component> elements;
        for( int i = 0; i < loadedSession; ++i ) {
            elements.add( new PlaceHolder("segmentBox" + String(i+1), false) );
        }
        resized();
    }
}

in resized(), the segmentBox nested section:

    FlexBox contentBox;
    {
        FlexBox segmentBox;
        segmentBox.flexWrap = FlexBox::Wrap::wrap;
        segmentBox.flexDirection = FlexBox::Direction::row;
        {
            for( int i = 0; i < elements.size(); ++i ) {
                auto &newElement = *elements[i];
                DBG( "name: " + newElement.getName() );
                addAndMakeVisible( newElement );
                segmentBox.items.add( FlexItem( newElement ).withFlex(1.0).withMargin({2.0}));
            }
        }
        contentBox.items.add( FlexItem(segmentBox).withFlex(1.0).withMargin({2.0}) );
    }

I just do not understand what is happening :frowning: Why is segment1 never shown???


#11

++i ?


#12

regarding i++ vs ++i: http://stackoverflow.com/questions/24901/is-there-a-performance-difference-between-i-and-i-in-c


#13

i know, but isnt it passing +1 to your for loop scope ?
edit :
what i mean is that your i reaches the scope already incremented


#14

wrong:


#15

If it works like that, I’ve been doing wrong code for the past 5 years or so. (So no, doing a for loop like that will work, but of course it doesn’t affect performance in any way. Doing ++i instead of i++ is just to get in the habit of using ++i in case the i is not a trivial integer but rather some more heavy iterator object which might be slow if i++ is done on it.)


#16

sure thing, but im not discussing luxury of getting into right habbit, just pointing that the i is already incremented while @matkatmusic is instantiating his placeholders. obvoiusly i should run the code myself, but its not possible at the moment. sorry i cant help now


#17

Look at the screenshot I posted. i is 0 the first time around. it is not 1 as you are assuming.


#18

honestly im shocked, considering my knowledge of course !


#19

Test it yourself:

for( int i = 0; i < 5; ++i ) {
    DBG( "++i: "+ String(i) );
}
for( int i = 0; i < 5; i++ ) {
    DBG( "i++: " + String(i) );
}


#20

yeah, i just tried. i blame myself. for my whole life ive been avoiding ++i in a simple for loop scenarios assuming that it will be already inremented while reaching the scope