Managing a Gui with nested Components


#1

Hi! Newb here.

I’m sorry for this non JUCE specific and more general C++/Software Design question, but I hope you don’t mind all to much…

I built my first Audio Application by following the tutorials and set up my GuiComponent as member of my AudioAppComponent. My Gui are a bunch of nested Components four levels deep, with Buttons and Labels as members of SubComponents, which are members of Header/Footer/SidebarComponents which are members of the GuiComponent.

I have set up the Logic of the Gui (like showing different screens) with Listener- and State-Patterns. But for handling inputs and outputs I need a class (lets call it Foo) that listens to Buttons and sets Labels far apart in my GuiTree.

When I make Foo a member of AudioAppComponent and it’s on the same level as GuiComponent, I can only come up with the following solutions:

  • make all Components public and make calls on them directly (although this is not the way the tutorials teach to do things)
  • make back-pointers for cross connection between Components
  • hand down variable references that Foo can set
  • save the state of the variables Foo is working on in a ValueTree and make the Components listen to that

I hope I described my problem clear enough and I’m sorry if this question is too basic. I just think there is some sort of design trick I haven’t heard about yet and that I am about to make things much to complex than they have to be…

Thanks for all that have read this far! I’m grateful for every answer! :bowing_man:


#2

After refactoring for a few weeks an reading some more books I have come to a solution and learned two C++ tricks that I’d like to share:

My AudioAppComponent now looks like this:

class MainContentComponent : public AudioAppComponent
{
public:
    MainContentComponent ()
    : foo (gui)
    {
        // Constructor things...
    };

    // AudioAppComponent  member functions...

private:
    Gui gui;
    Foo foo //listens to changes in Gui and sets Gui elements
};

with Gui and Foo

class Gui : public Component,
             public GuiInterface
{
public:
    Gui ()
    {
        // ...
    };

// Component functions, GuiInterface implementations
// and child Components making up the GUI

};
class Foo : public Button::Listener
{
public:
    Foo (GuiInterface& gui)
    : interface (gui)
    {
        // Constructor things...
    };

    // Butto::Listener implementation...

private:
    GuiInterface& interface;
};

with GuiInterface being a virtual class defining what Gui must be able to react to.
If for example a Button click should change the text of a Label, Foo could call

void buttonClicked (Button* b) override
{
    interface.changeLabelText();
};

where changeLabelText() is a virtual function of GuiInterface that Gui has to define.

By using an Interface, I could separate the things Gui has to be able to do, with the actual Layout and Look of the GUI, which is now the only thing defined in the class.

To add Foo as a Listener to the buttons in Gui, I still have to somehow call button.addListener ( /* address of Foo */ ); and for this I’ve come up with this:

At the end of the MainContentComponent constructor I call a Gui function called registerListeners ().

void Gui::registerListeners()
{
    Component* parent = this->getParentComponent();
    MainContentComponent* parentAsMCC = dynamic_cast<MainContentComponent*>(parent);

    guiButton.addListener ( &(parentAsMCC->foo) );
}

First, a Component type pointer to MainContentComponent is created. Then that pointer is turned from a Component the to a MainContentComponent type pointer. With that pointer can get the address of the MainContentComponent member foo.

But since foo is a private member of MainContentComponent, Gui has no access to it. So for this to work friend Gui has to be added in the class declaration of MainContentComponent, so Gui has access to all private members.

Maybe this is somehow helpful for others. If you are a beginner like me and you have trouble getting pointers to parent objects, you might look into casting some Component pointers received by getParentComponent() (although this only works for Component Objects…)