Can anyone help me troubleshoot this code?


#1

My primary objective for a while now has been to be able to create a nested system of Flexboxes and GroupComponent to create a flexible panel system. I’ve gotten reasonably close.

This was my last working code (which can be loaded as a standalone main.cpp for a Juce project):
Link to Github of file

/*
==============================================================================

This file was auto-generated and contains the startup code for a PIP.

==============================================================================
*/

#include "../JuceLibraryCode/JuceHeader.h"

///============================
///BEGINNING OF USUAL PROJECT HEADER


#pragma once

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 LabeledGroup : public GroupComponent

{
public:
	LabeledGroup(const String& name)
	{
		setText(name);
		setTextLabelPosition(Justification::centredTop);

		addAndMakeVisible(dummy1);
		addAndMakeVisible(dummy2);
		addAndMakeVisible(dummy3);
		addAndMakeVisible(dummy4);

	}

	void resized() override
	{
		//setBounds(getLocalBounds().reduced(10));

		FlexBox knobBox1;
		knobBox1.flexWrap = FlexBox::Wrap::wrap;
		knobBox1.justifyContent = FlexBox::JustifyContent::flexStart;
		knobBox1.alignContent = FlexBox::AlignContent::flexStart;

		Array<LabeledSlider*> knobs1;
		knobs1.add(&dummy1, &dummy2, &dummy3, &dummy4);

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

		FlexBox fb1;
		fb1.flexDirection = FlexBox::Direction::column;
		fb1.items.add(FlexItem(knobBox1).withFlex(2.5));
		fb1.performLayout(getLocalBounds().reduced(15).toFloat());

		

	}


private:
	LabeledSlider dummy1{ "Dummy 1" };
	LabeledSlider dummy2{ "Dummy 2" };
	LabeledSlider dummy3{ "Dummy 3" };
	LabeledSlider dummy4{ "Dummy 4" };

};


//==============================================================================
//MAINCONTENTCOMPONENT
//==============================================================================

class MainContentComponent : public Component

{
public:
	MainContentComponent()

	{
		addAndMakeVisible(group1);
		addAndMakeVisible(group2);
	
		level.slider.setRange(0.0, 1.0);
		level.slider.setNumDecimalPlacesToDisplay(1);
		level.slider.onValueChange = [this] { targetLevel = (float)level.slider.getValue(); };
		addAndMakeVisible(level);


		setSize(600, 600);
	}

	~MainContentComponent()
	{
		//		shutdownAudio();
	}


	void resized() override
	{
	    //group1.setBounds(getLocalBounds().expanded(10));
		//group2.setBounds(getLocalBounds().expanded(10));
		
		FlexBox groupBox1;
		groupBox1.flexWrap = FlexBox::Wrap::wrap;
		groupBox1.justifyContent = FlexBox::JustifyContent::flexStart;
		groupBox1.alignContent = FlexBox::AlignContent::flexStart;
		

		Array<LabeledGroup*> groups1array;
		groups1array.add(&group1, &group2);

		for (auto *g : groups1array)
		groupBox1.items.add(FlexItem(*g).withMinHeight(100.0f).withMinWidth(350.0f).withFlex(3).withMargin(5));

		FlexBox fb1;
		fb1.flexDirection = FlexBox::Direction::column;
		fb1.items.add(FlexItem(groupBox1).withFlex(3));
		fb1.performLayout(getLocalBounds().toFloat());
	
		
	}




private:

	float currentLevel = 0.1f, targetLevel = 0.1f;

	LabeledGroup group1{ "Group 1" };
	LabeledGroup group2{ "Group 2" };
	LabeledSlider level{ "Level" };


	JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainContentComponent)
};





///============================
///BEGINNING OF USUAL MAIN.CPP
class Application : public JUCEApplication
{
public:

	Application() {}

	const String getApplicationName() override { return "SineSynthTutorial"; }
	const String getApplicationVersion() override { return "3.0.0"; }

	void initialise(const String&) override { mainWindow.reset(new MainWindow("SineSynthTutorial", new MainContentComponent(), *this)); }
	void shutdown() override { mainWindow = nullptr; }

private:
	class MainWindow : public DocumentWindow
	{
	public:
		MainWindow(const String& name, Component* c, JUCEApplication& a)
			: DocumentWindow(name, Desktop::getInstance().getDefaultLookAndFeel()
				.findColour(ResizableWindow::backgroundColourId),
				DocumentWindow::allButtons),
			app(a)
		{
			setUsingNativeTitleBar(true);
			setContentOwned(c, true);

#if JUCE_ANDROID || JUCE_IOS
			setFullScreen(true);
#else
			setResizable(true, false);
			setResizeLimits(300, 250, 10000, 10000);
			centreWithSize(getWidth(), getHeight());
#endif

			setVisible(true);
		}

		void closeButtonPressed() override
		{
			app.systemRequestedQuit();
		}

	private:
		JUCEApplication & app;

		//==============================================================================
		JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainWindow)
	};

	std::unique_ptr<MainWindow> mainWindow;
};

//==============================================================================
START_JUCE_APPLICATION(Application)

@daniel helped come up with a lot of the original code I used there. It works but I had to cheat in the coding and hardcode a lot of parameters into the class definitions (as I still lack skill with C++), so it’s not yet a final working solution.

I chatted with @Matkatmusic on Discord who helped offer some ideas and code for how to put it together in a working format. This is the best I’ve been able to compose from that:

Link to Github file

/*
==============================================================================

This file was auto-generated and contains the startup code for a PIP.

==============================================================================
*/

#include "../JuceLibraryCode/JuceHeader.h"

//===========================
//USUAL PROJECT HEADER
//===========================

#pragma once

//===LABELEDSLIDER===//

class LabeledSlider : public GroupComponent
{
public:
	LabeledSlider(const String& name, Slider::SliderStyle style, Slider::TextEntryBoxPosition textPos)
	{
		setText(name);
		setTextLabelPosition(Justification::centredTop);
		slider.reset(new Slider(style, textPos));

		addAndMakeVisible(slider.get());
	}

	/*
	void setSliderRange(Range<float> r) 
	{
		slider->setRange(r);
		void setSliderDecimal(int num) { slider->setNumDecimalPlacesToDisplay(num); }
		void setOnValueChange(std::function<void()> func) { slider->onValueChange = std::move(func); }
	}
	*/

	void resized() override
	{
		slider.get()->setBounds(getLocalBounds().reduced(10));
	}

private:
	std::unique_ptr<Slider> slider;
	
};

//===LABELEDGROUP===//

class LabeledGroup : public GroupComponent
{
public:
	LabeledGroup(const String& name, Array<LabeledSlider*> sliderArray) //: sliders(sliderArray)
	{

	std::unique_ptr<LabeledGroup> group;
	
		setText(name);
		setTextLabelPosition(Justification::centredTop);

		for (auto* slider : sliders)
		{
			addAndMakeVisible(slider);
		}

	}

	void resized() override
	{
		setBounds(getLocalBounds().reduced(10));

		FlexBox knobBox;
		knobBox.flexWrap = FlexBox::Wrap::wrap;
		knobBox.justifyContent = FlexBox::JustifyContent::flexStart;
		knobBox.alignContent = FlexBox::AlignContent::flexStart;
			
		for (auto *k : knobs)
			knobBox.items.add(FlexItem(*k).withMinHeight(80.0f).withMinWidth(80.0f).withFlex(1));

		FlexBox fb1;
		fb1.flexDirection = FlexBox::Direction::column;
		fb1.items.add(FlexItem(knobBox).withFlex(2.5));
		fb1.performLayout(getLocalBounds().reduced(15).toFloat());

	}

private:
	OwnedArray<LabeledSlider*> sliders;
	OwnedArray<LabeledSlider> knobs;

};


//====================
//MAINCONTENTCOMPONENT
//====================

class MainContentComponent : public Component

{
public:
	MainContentComponent()

	{
		/*
		auto* attackLS = sliderObjects.add(new LabeledSlider("Attack", Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow));
		slider->setRange(0, 3);
		slider->setNumDecimal(1);
		slider->setOnValueChange(5);

		auto* decayLS = sliderObjects.add(new LabeledSlider("Decay", Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow));
		slider->setRange(0, 3);
		slider->setNumDecimal(1);
		slider->setOnValueChange(5);
		
		auto* releaseLS = sliderObjects.add(new LabeledSlider("Release", Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow));
		slider->setRange(0, 3);
		slider->setNumDecimal(1);
		slider->setOnValueChange(5);

		Array<LabeledSlider*> envelope = { attackLS, decayLS, releaseLS };
		*/

		Array<LabeledSlider*> group1array =
		{
			sliderObjects.add(new LabeledSlider("attack",Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow)),
			sliderObjects.add(new LabeledSlider("decay", Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow)),
			sliderObjects.add(new LabeledSlider("sustain", Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow)),
			sliderObjects.add(new LabeledSlider("release", Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow))
		};

		LabeledGroup group1("My Group", group1array);

		//auto group1 = std::make_unique<LabeledGroup>("Group1", {});
		std::unique_ptr<LabeledGroup> group1;
		std::unique_ptr<LabeledGroup> group2;
		
		setSize(600, 600);
	}
	

	void resized() override
	{
	    //group1.setBounds(getLocalBounds().expanded(10));
		//group2.setBounds(getLocalBounds().expanded(10));
		
		FlexBox groupBox;
		groupBox.flexWrap = FlexBox::Wrap::wrap;
		groupBox.justifyContent = FlexBox::JustifyContent::flexStart;
		groupBox.alignContent = FlexBox::AlignContent::flexStart;
		
		for (auto *g : groups)
		groupBox.items.add(FlexItem(*g).withMinHeight(100.0f).withMinWidth(350.0f).withFlex(3).withMargin(5));

		FlexBox fb1;
		fb1.flexDirection = FlexBox::Direction::column;
		fb1.items.add(FlexItem(groupBox).withFlex(3));
		fb1.performLayout(getLocalBounds().toFloat());
	
	}

	~MainContentComponent()
	{
		//		shutdownAudio();
	}

private:

	OwnedArray < LabeledGroup > groups;
	std::unique_ptr<LabeledGroup> group1;
	OwnedArray<LabeledSlider> sliderObjects;

};


//=====================
//USUAL MAIN.CPP
//=====================

class Application : public JUCEApplication
{
public:

	Application() {}

	const String getApplicationName() override { return "SineSynthTutorial"; }
	const String getApplicationVersion() override { return "3.0.0"; }

	void initialise(const String&) override { mainWindow.reset(new MainWindow("SineSynthTutorial", new MainContentComponent(), *this)); }
	void shutdown() override { mainWindow = nullptr; }

private:
	class MainWindow : public DocumentWindow
	{
	public:
		MainWindow(const String& name, Component* c, JUCEApplication& a)
			: DocumentWindow(name, Desktop::getInstance().getDefaultLookAndFeel()
				.findColour(ResizableWindow::backgroundColourId),
				DocumentWindow::allButtons),
			app(a)
		{
			setUsingNativeTitleBar(true);
			setContentOwned(c, true);

#if JUCE_ANDROID || JUCE_IOS
			setFullScreen(true);
#else
			setResizable(true, false);
			setResizeLimits(300, 250, 10000, 10000);
			centreWithSize(getWidth(), getHeight());
#endif

			setVisible(true);
		}

		void closeButtonPressed() override
		{
			app.systemRequestedQuit();
		}

	private:
		JUCEApplication & app;

		//==============================================================================
		JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainWindow)
	};

	std::unique_ptr<MainWindow> mainWindow;
};

//==============================================================================
START_JUCE_APPLICATION(Application)

It’s only actually giving one error in Visual Studio now:

I’m not sure how to fix this error. Anyone know how to fix this or make it work? Any further suggestions?

Thanks


#3

The error is because it’s trying to add a LabeledSlider** instead of a LabeledSlider* if you drop the * after auto in your for loop I suspect that will compile, normally when using auto I prefer to be safe by using auto&.

To explain a little more for each element in the for loop…
auto will give you a copy therefore it will be LabledSlider*
auto* will give you a pointer therefore it will be LabledSlider**
auto& will give you a reference therefore it will be LabledSlider*&

A couple of other general points to make.

Array<LabeledSlider*> sliderArray should probably be const Array<LabeledSlider*>& sliderArray. That way you’re not making an unnecessary copy of the array, instead you will take a reference to the array and promise not to change it in any way.

Most of these classes seem to be about attaching a label to a component, have you tried using the Label’s attachToComponent() method? (https://docs.juce.com/master/classLabel.html#a3c2397c0da1249f9e27e2279e0f2d4eb)


#4

Thanks Anthony. Removing the * from:

	for (auto* slider : sliders)
	{
		addAndMakeVisible(slider);
	}

Did not get rid of the error, but removing the * from:

private:
	OwnedArray<LabeledSlider*> sliders;
	OwnedArray<LabeledSlider> knobs;

Did get rid of the error. I don’t know if that was correct though. Changing the other part to const Array&<LabeledSlider*>& sliderArray just added a new warning so for simplicity I left that out.

It will now compile but it just makes a blank screen. Ie. None of the knobs or groups are showing up. Any ideas for why? The only warning I get now is: “declaration of ‘group1’ hides class member”, referring to I believe that I declared ‘group1’ in two different ways:

LabeledGroup group1("My Group", group1array);

std::unique_ptr<LabeledGroup> group1;

Commenting out the unique_ptr definition gets rid of that warning, and now I have no warnings nor errors but no knobs or groups either being made.

Any thoughts?

As for the use of GroupComponent to label the knobs and groups, this was daniel’s suggested way to do it. In the long run, I think I would aim to rename the “LabeledSlider” class to “LabeledGUIElement” and have it create sliders, combo boxes, or buttons as needed. Then I can still do the automatic arraying based on class and FlexBoxing of anything from this class within each LabeledGroup.

I’m not sure if attachToComponent() will really help improve things, as I will still need all the arrays and GroupComponent for defining groups, etc. Correct me if I’m wrong. I just want this to work.

/*
==============================================================================

This file was auto-generated and contains the startup code for a PIP.

==============================================================================
*/

#include "../JuceLibraryCode/JuceHeader.h"

//===========================
//USUAL PROJECT HEADER
//===========================

#pragma once

//===LABELEDSLIDER===//

class LabeledSlider : public GroupComponent
{
public:
	LabeledSlider(const String& name, Slider::SliderStyle style, Slider::TextEntryBoxPosition textPos)
	{
		setText(name);
		setTextLabelPosition(Justification::centredTop);
		slider.reset(new Slider(style, textPos));

		addAndMakeVisible(slider.get());
	}

	/*
	void setSliderRange(Range<float> r) 
	{
		slider->setRange(r);
		void setSliderDecimal(int num) { slider->setNumDecimalPlacesToDisplay(num); }
		void setOnValueChange(std::function<void()> func) { slider->onValueChange = std::move(func); }
	}
	*/

	void resized() override
	{
		slider.get()->setBounds(getLocalBounds().reduced(10));
	}

private:
	std::unique_ptr<Slider> slider;
	
};

//===LABELEDGROUP===//

class LabeledGroup : public GroupComponent
{
public:
	LabeledGroup(const String& name, Array<LabeledSlider*> sliderArray) //: sliders(sliderArray)
	{

	std::unique_ptr<LabeledGroup> group;
	
		setText(name);
		setTextLabelPosition(Justification::centredTop);

		for (auto* slider : sliders)
		{
			addAndMakeVisible(slider);
		}

	}

	void resized() override
	{
		setBounds(getLocalBounds().reduced(10));

		FlexBox knobBox;
		knobBox.flexWrap = FlexBox::Wrap::wrap;
		knobBox.justifyContent = FlexBox::JustifyContent::flexStart;
		knobBox.alignContent = FlexBox::AlignContent::flexStart;
			
		for (auto *k : knobs)
			knobBox.items.add(FlexItem(*k).withMinHeight(80.0f).withMinWidth(80.0f).withFlex(1));

		FlexBox fb1;
		fb1.flexDirection = FlexBox::Direction::column;
		fb1.items.add(FlexItem(knobBox).withFlex(2.5));
		fb1.performLayout(getLocalBounds().reduced(15).toFloat());

	}

private:
	OwnedArray<LabeledSlider> sliders;
	OwnedArray<LabeledSlider> knobs;

};


//====================
//MAINCONTENTCOMPONENT
//====================

class MainContentComponent : public Component

{
public:
	MainContentComponent()

	{
		/*
		auto* attackLS = sliderObjects.add(new LabeledSlider("Attack", Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow));
		slider->setRange(0, 3);
		slider->setNumDecimal(1);
		slider->setOnValueChange(5);

		auto* decayLS = sliderObjects.add(new LabeledSlider("Decay", Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow));
		slider->setRange(0, 3);
		slider->setNumDecimal(1);
		slider->setOnValueChange(5);
		
		auto* releaseLS = sliderObjects.add(new LabeledSlider("Release", Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow));
		slider->setRange(0, 3);
		slider->setNumDecimal(1);
		slider->setOnValueChange(5);

		Array<LabeledSlider*> envelope = { attackLS, decayLS, releaseLS };
		*/

		Array<LabeledSlider*> group1array =
		{
			sliderObjects.add(new LabeledSlider("attack",Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow)),
			sliderObjects.add(new LabeledSlider("decay", Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow)),
			sliderObjects.add(new LabeledSlider("sustain", Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow)),
			sliderObjects.add(new LabeledSlider("release", Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow))
		};

		LabeledGroup group1("My Group", group1array);

		//auto group1 = std::make_unique<LabeledGroup>("Group1", {});

		
		setSize(600, 600);
	}
	

	void resized() override
	{
	    //group1.setBounds(getLocalBounds().expanded(10));
		//group2.setBounds(getLocalBounds().expanded(10));
		
		FlexBox groupBox;
		groupBox.flexWrap = FlexBox::Wrap::wrap;
		groupBox.justifyContent = FlexBox::JustifyContent::flexStart;
		groupBox.alignContent = FlexBox::AlignContent::flexStart;
		
		for (auto *g : groups)
		groupBox.items.add(FlexItem(*g).withMinHeight(100.0f).withMinWidth(350.0f).withFlex(3).withMargin(5));

		FlexBox fb1;
		fb1.flexDirection = FlexBox::Direction::column;
		fb1.items.add(FlexItem(groupBox).withFlex(3));
		fb1.performLayout(getLocalBounds().toFloat());
	
	}

	~MainContentComponent()
	{
		//		shutdownAudio();
	}

private:

	OwnedArray < LabeledGroup > groups;
	OwnedArray<LabeledSlider> sliderObjects;
	std::unique_ptr<LabeledGroup> group1;
	std::unique_ptr<LabeledGroup> group2;
};


//=====================
//USUAL MAIN.CPP
//=====================

class Application : public JUCEApplication
{
public:

	Application() {}

	const String getApplicationName() override { return "SineSynthTutorial"; }
	const String getApplicationVersion() override { return "3.0.0"; }

	void initialise(const String&) override { mainWindow.reset(new MainWindow("SineSynthTutorial", new MainContentComponent(), *this)); }
	void shutdown() override { mainWindow = nullptr; }

private:
	class MainWindow : public DocumentWindow
	{
	public:
		MainWindow(const String& name, Component* c, JUCEApplication& a)
			: DocumentWindow(name, Desktop::getInstance().getDefaultLookAndFeel()
				.findColour(ResizableWindow::backgroundColourId),
				DocumentWindow::allButtons),
			app(a)
		{
			setUsingNativeTitleBar(true);
			setContentOwned(c, true);

#if JUCE_ANDROID || JUCE_IOS
			setFullScreen(true);
#else
			setResizable(true, false);
			setResizeLimits(300, 250, 10000, 10000);
			centreWithSize(getWidth(), getHeight());
#endif

			setVisible(true);
		}

		void closeButtonPressed() override
		{
			app.systemRequestedQuit();
		}

	private:
		JUCEApplication & app;

		//==============================================================================
		JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainWindow)
	};

	std::unique_ptr<MainWindow> mainWindow;
};

//==============================================================================
START_JUCE_APPLICATION(Application)

JUCE tutoring or problem solving services?
#5

Did not get rid of the error, but removing the * from:

private:
	OwnedArray<LabeledSlider*> sliders;
	OwnedArray<LabeledSlider> knobs;

Well spotted, I missed that, yes that’s definitely not right.

Read the documentation to see why…

An array designed for holding objects.

This holds a list of pointers to objects, and will automatically delete the objects when they are removed from the array, or when the array is itself deleted.

Declare it in the form: OwnedArray<MyObjectClass>

…and then add new objects, e.g. myOwnedArray.add (new MyObjectClass());

After adding objects, they are ‘owned’ by the array and will be deleted when removed or replaced.

So when it’s set to LabeledSlider you could do something like sliders.add (new LabeledSlider());, in other words it’s already expecting a pointer to LabeledSlider. By doing OwnedArray<LabeledSlider*> sliders; it will be expecting a pointer to another pointer aka LabeledSlider**

Changing the other part to const Array&<LabeledSlider*>& sliderArray just added a new warning so for simplicity I left that out.

You’ve added an extra ampersand it should be const Array<LabeledSlider*>& sliderArray

It will now compile but it just makes a blank screen. Ie. None of the knobs or groups are showing up. Any ideas for why? The only warning I get now is: “declaration of ‘group1’ hides class member”, referring to I believe that I declared ‘group1’ in two different ways:

I’ve added some comments to your code to explain at least a couple of initial issues, there’s likely more.

// creating a local array here is fine
Array<LabeledSlider*> group1array =
{
    // sliderObjects will now take these new LabeledSlider objects, which is OK for now
	sliderObjects.add(new LabeledSlider("attack",Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow)),
	sliderObjects.add(new LabeledSlider("decay", Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow)),
	sliderObjects.add(new LabeledSlider("sustain", Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow)),
	sliderObjects.add(new LabeledSlider("release", Slider::RotaryHorizontalVerticalDrag, Slider::TextBoxBelow))
};

// hmm but now group1 will also try to take ownership of these LabeledSlider objects
// additionally to that it will actually delete them when this function finishes as this is a local variable
// this variable needs to be a member and addAndMakeVisible (myGroup1Member) needs to be called so it can be seen
// also as you have already pointed out this local variable hides the class member with the same name
LabeledGroup group1("My Group", group1array);

If I were you I would consider breaking this whole thing up into smaller tasks starting from scratch.

  • Add a component and make it visible
  • Make a custom component and make it visible
  • Add another component to the custom component and make it visible
  • Store the other component in an OwnerArray
  • Pass multiple components to the custom component
  • etc.

With each step make the thing compile and make sure you’re happy with it visually.