Which is best for my use: Grid or FlexBox? Simplest way to get some knobs organized on the screen?

Hey jimc, thanks for the feedback. I think we developed an even better method using FlexBox over here thanks to daniel’s help:

Here is the code at present (working from the SineSynth tutorial):

class LabeledSlider : public GroupComponent

{
    public:
    LabeledSlider (const String& name)
    {
        setText (name);
        setTextLabelPosition (Justification::centredTop);
        addAndMakeVisible (slider);
    }

    void resized() override
    {
        slider.setBounds (getLocalBounds().reduced (10));
    }
    
    Slider slider 
    { 
        Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow 
    };
    

};



class MainContentComponent : public AudioAppComponent
{
public:
    MainContentComponent()
	
    {
    /*
	addAndMakeVisible(frequency);
    addAndMakeVisible(level);
	addAndMakeVisible(dummy1);
	addAndMakeVisible(dummy2);
	addAndMakeVisible(dummy3);
	addAndMakeVisible(dummy4);
	addAndMakeVisible(dummy5);
	addAndMakeVisible(dummy6);
	addAndMakeVisible(dummy7);
	addAndMakeVisible(dummy8);
	addAndMakeVisible(dummy9);
	*/
		/*
		frequency.slider.setRange(20.0, 20000.0);
		frequency.slider.setSkewFactorFromMidPoint(500.0);
		frequency.slider.setNumDecimalPlacesToDisplay(1);
		frequency.slider.setValue(currentFrequency, dontSendNotification);
		frequency.slider.onValueChange = [this] { targetFrequency = frequency.slider.getValue(); };
		frequency.slider.setTextBoxStyle(Slider::TextBoxBelow, false, 100, 20);
		frequency.slider.setRange(50.0, 5000.0);
		frequency.slider.setSkewFactorFromMidPoint(500.0);
		frequency.slider.setNumDecimalPlacesToDisplay(1);

		level.slider.setRange(0.0, 1.0);
		level.slider.onValueChange = [this] { targetLevel = (float)level.slider.getValue(); };
		*/

		LabeledSlider* control = new LabeledSlider("Frequency");
		control->slider.setRange(20.0, 20000.0);
		control->slider.setSkewFactorFromMidPoint(500.0);
		control->slider.setNumDecimalPlacesToDisplay(1);
		control->slider.setValue(currentFrequency, dontSendNotification);
		control->slider.onValueChange = [this] { targetFrequency = frequency.slider.getValue(); };
		control->slider.setTextBoxStyle(Slider::TextBoxBelow, false, 100, 20);
		control->slider.setRange(50.0, 5000.0);
		control->slider.setSkewFactorFromMidPoint(500.0);
		control->slider.setNumDecimalPlacesToDisplay(1);
		addAndMakeVisible(knobs.add(control));

		control = new LabeledSlider("Level");
		control->slider.setRange(0.0, 1.0);
		control->slider.onValueChange = [this] { targetLevel = (float)level.slider.getValue(); };
		addAndMakeVisible(knobs.add(control));

		control = new LabeledSlider("Dummy1");
		addAndMakeVisible(knobs.add(control));

		control = new LabeledSlider("Dummy2");
		addAndMakeVisible(knobs.add(control));

		control = new LabeledSlider("Dummy3");
		addAndMakeVisible(knobs.add(control));

		control = new LabeledSlider("Dummy4");
		addAndMakeVisible(knobs.add(control));

		control = new LabeledSlider("Dummy5");
		addAndMakeVisible(knobs.add(control));

		control = new LabeledSlider("Dummy6");
		addAndMakeVisible(knobs.add(control));

		control = new LabeledSlider("Dummy7");
		addAndMakeVisible(knobs.add(control));

		control = new LabeledSlider("Dummy8");
		addAndMakeVisible(knobs.add(control));

		control = new LabeledSlider("Dummy9");
		addAndMakeVisible(knobs.add(control));

    setSize (600, 600);
    setAudioChannels (0, 2); // no inputs, two outputs
    }

    ~MainContentComponent()
    {
        shutdownAudio();
    }

    void resized() override
    {
			
		//==============================================================================
		FlexBox knobBox;
		knobBox.flexWrap = FlexBox::Wrap::wrap;
		knobBox.justifyContent = FlexBox::JustifyContent::flexStart;
		knobBox.alignContent = FlexBox::AlignContent::flexStart;

		//Array<LabeledSlider*> knobs;
		//knobs.add(&frequency, &level, &dummy1, &dummy2, &dummy3, &dummy4, &dummy5, &dummy6, &dummy7, &dummy8, &dummy9);

		for (auto* k : knobs)
			knobBox.items.add(FlexItem(*k).withMinHeight(80.0f).withMinWidth(80.0f).withFlex(1));

		//==============================================================================
		FlexBox fb;
		fb.flexDirection = FlexBox::Direction::column;
		fb.items.add(FlexItem(knobBox).withFlex(2.5));
		fb.performLayout(getLocalBounds().toFloat());
	

    }

    inline void updateAngleDelta()
    {
        auto cyclesPerSample = currentFrequency / currentSampleRate;
        angleDelta = cyclesPerSample * 2.0 * MathConstants<double>::pi;
    }

    void prepareToPlay (int, double sampleRate) override
    {
        currentSampleRate = sampleRate;
        updateAngleDelta();
    }

    void releaseResources() override {}

    void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
    {
        auto* leftBuffer  = bufferToFill.buffer->getWritePointer (0, bufferToFill.startSample);
        auto* rightBuffer = bufferToFill.buffer->getWritePointer (1, bufferToFill.startSample);

        auto localTargetFrequency = targetFrequency;

        if (targetFrequency != currentFrequency)
        {
            auto frequencyIncrement = (targetFrequency - currentFrequency) / bufferToFill.numSamples;

            for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
            {
                auto currentSample = (float) std::sin (currentAngle);
                currentFrequency += frequencyIncrement;
                updateAngleDelta();
                currentAngle += angleDelta;
                leftBuffer[sample]  = currentSample;
                rightBuffer[sample] = currentSample;
            }

            currentFrequency = localTargetFrequency;
        }
        else
        {
            for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
            {
                auto currentSample = (float) std::sin (currentAngle);
                currentAngle += angleDelta;
                leftBuffer[sample]  = currentSample;
                rightBuffer[sample] = currentSample;
            }
        }

        auto localTargetLevel = targetLevel;
        bufferToFill.buffer->applyGainRamp (bufferToFill.startSample, bufferToFill.numSamples, currentLevel, localTargetLevel);
        currentLevel = localTargetLevel;
	}

private:
	double currentSampleRate = 0.0, currentAngle = 0.0, angleDelta = 0.0;
	double currentFrequency = 500.0, targetFrequency = 500.0;
	float currentLevel = 0.1f, targetLevel = 0.1f;
	int rotaryDiam = 100;
		
	LabeledSlider frequency{ "Frequency" };
	LabeledSlider level{ "Level" };
	LabeledSlider dummy1{ "Dummy 1" };
	LabeledSlider dummy2{ "Dummy 2" };
	LabeledSlider dummy3{ "Dummy 3" };
	LabeledSlider dummy4{ "Dummy 4" };
	LabeledSlider dummy5{ "Dummy 5" };
	LabeledSlider dummy6{ "Dummy 6" };
	LabeledSlider dummy7{ "Dummy 7" };
	LabeledSlider dummy8{ "Dummy 8" };
	LabeledSlider dummy9{ "Dummy 9" };

	OwnedArray<LabeledSlider> knobs;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

I commented out some parts rather than deleting them as I think those may be a necessary approach in other situations.

Now, I need to work out a good way to nest a layer of Flexboxes over top of these Flexboxes, so I can have “groups” of knobs.

I think what I will have to do is:

  • Define a new GroupComponent called LabeledGroup.
  • Manually assign the knobs to a bunch of arrays (one for each “group”).
  • Set up a FlexBox for each array manually also so each group gets FlexBoxed within it.
  • Then use the OwnedArray approach above for automatically sorting the groups of knobs all out.

That’s my challenge now. Otherwise it’s working very well as is and it’s pretty simple to use.