Juce GUI Environment - Some thoughts on it


#1

Hi all,

As a beginner in Juce framework, I many times tried to understand the GUI environment. This means its general design philosophy and implementation approach.

Unfortunately, I did not find an easy and comprehensible document to guide my effort.

So, in the following plain description, I am trying to describe the method I used to implement a Juce GUI environment, based on what I understood from the existing documentation.

Please note that the real meaning of this post is to get the useful opinion of the experienced members of the forum in order to be better and to help any other Juce beginner.

Consider we have to implement a GUI environment, consisting of:

  • One main content component, shown in the Main Window
  • Two distinct areas within this component, named as ControlPanel and OperationPanel.

My approach is based on the following schema:

  1. We create a corresponding class for each distinct area, with their .cpp and .h files
  2. We design the appropriate GUI with the Introjucer for each area, starting at 0,0 position.
  3. In the MainContentComponent, we define a member variable for each class.
  4. In the MainContentComponent’s constructor, we also include the constructors of the above two classes.
  5. In each class constructor, we set the size of the entire subcomponent to be equal to the bottom-right coordinates of the last bottom-right GUI object.
  6. After that, in the MainContentComponent’s constructor we set the size of the entire MainContentComponent, accordingly to the above steps.
  7. And finally, we can set the size of the mainWindow respectively.

Using the same logic, we can to create many other MainContentComponets.   

Under this approach:

  1. We can design easily and maintain independently, each GUI's part for this specific app.
  2. We can reuse each separate part into any other app, without having to do major changes.

However, all the above is an approach of a beginner. 

Now, my questions are:

  • Probably there are many other ways to do this. But, is this an appropriate approach to design GUI with Juce environment?
  • If not, why?        

 

Thanks in advance for every comment and idea

George

        


#2

I am by no means a JUCE "expert", but I see a few problems with your approach.

 

  1. We create a corresponding class for each distinct area, with their .cpp and .h files

    It's ok as a beginning, but you generally do not want to do that. Don't think it classes, think in instances. Don't repeat yourself, it's a recipe for a disaster, with GUIs in particular.
     
  2. We design the appropriate GUI with the Introjucer for each area, starting at 0,0 position.

    That's fine.
     
  3. In the MainContentComponent, we define a member variable for each class.

    Well, only if you are really using an instance of this class at this point. If not, it's a pure waste of resources and this tends to stack up quickly in GUIs.
     
  4. In the MainContentComponent’s constructor, we also include the constructors of the above two classes.

    Generally, you want to do this in an implicit manner and fool-proof, google for "c++ RAII". Juce heavilly promotes the approach in its own code and examples.
     
  5. In each class constructor, we set the size of the entire subcomponent to be equal to the bottom-right coordinates of the last bottom-right GUI object.

    No, you shouldn't. Use the resize() "handler" of the parent component instance.
     
  6. After that, in the MainContentComponent’s constructor we set the size of the entire MainContentComponent, accordingly to the above steps.

    No, just use the resize() handler, call setBounds() whenever the bounds of the parent component change and let the "magic" happen.
     
  7. And finally, we can set the size of the mainWindow respectively.

    Same as 6)

 

With GUIs in general, you want to rely on inheritance to avoid any sort of redundant code. Much like databases need to be "normalized" in order to work/scale and maintain properly (no double code). It's very similar with "true" code: If your code isn't "normalized", it will be an insane waste of time to scale and/or extend any aspect of it at a later point.

Have a look at the MVC pattern (and its variants).


#3

I am not a native English speaker, so maybe I can’t be precise on what I mean.

Obviously I did not yet understand the basic design philosophy of the Juce GUI

Thus, I attach here a piece of code for the  MainContentComponent, OperationPanel and ControlPanel, files .

The basic idea is to hold independent each part of the GUI, and in the Main Component the only I have to do is to position correctly each distinct part.

If you wish have a look on the code.

Thanks a lot for your attention

George

 

MainContentComponent.cpp

/*

in the .h file :


class MainContentComponent   : public Component
{
public:
    //==============================================================================
    MainContentComponent();
    ~MainContentComponent();
    void paint (Graphics&);
    void resized();
private:
    OperationPanel OperationPanel;
    ControlPanel ControlPanel;
}
*/

 

/*

  ==============================================================================
    This file was auto-generated!
  ==============================================================================
*/
#include "MainComponent.h"

//==============================================================================
MainContentComponent::MainContentComponent()
    :OperationPanel(),
    ControlPanel()
{
    addAndMakeVisible(&OperationPanel);
    addAndMakeVisible(&ControlPanel);
    setSize (600, 400);
}
MainContentComponent::~MainContentComponent()
{
}
void MainContentComponent::paint (Graphics& g)
{
    g.fillAll (Colour (0xffeeddff));
    g.setFont (Font (16.0f));
    g.setColour (Colours::black);
 //   g.drawText ("Hello World!", getLocalBounds(), Justification::centred, true);
}
void MainContentComponent::resized()
{
    // This is called when the MainContentComponent is resized.
    // If you add any child components, this is where you should
    // update their positions.
    ControlPanel.setBounds(OperationPanel.getX(),
                           OperationPanel.getHeight() + OperationPanel.getY() + 2,
                           ControlPanel.getWidth(),
                           ControlPanel.getHeight());
}
 

OperationPanel.cpp 

// in the .h file :

/*
class OperationPanel  : public Component,
                        public ButtonListener

e.t.c.

*/


OperationPanel::OperationPanel ()
{
    addAndMakeVisible (btn_OnOff = new ToggleButton ("btn_OnOff"));
    btn_OnOff->setButtonText (TRANS("On/Off"));
    btn_OnOff->addListener (this);
    btn_OnOff->setColour (ToggleButton::textColourId, Colours::blue);
    addAndMakeVisible (btn_InOut = new ToggleButton ("btn_InOut"));
    btn_InOut->setButtonText (TRANS("In/Out"));
    btn_InOut->addListener (this);
    addAndMakeVisible (OperationPanelGroup = new GroupComponent ("OperationPanelGroup",
                                                                 TRANS("Operation Panel")));
    OperationPanelGroup->setTextLabelPosition (Justification::centredLeft);

    //[UserPreSize]
    //[/UserPreSize]
    setSize (600, 400);

    //[Constructor] You can add your own custom stuff here..
    //[/Constructor]
}
OperationPanel::~OperationPanel()
{
    //[Destructor_pre]. You can add your own custom destruction code here..
    //[/Destructor_pre]
    btn_OnOff = nullptr;
    btn_InOut = nullptr;
    OperationPanelGroup = nullptr;

    //[Destructor]. You can add your own custom destruction code here..
    //[/Destructor]
}
//==============================================================================
void OperationPanel::paint (Graphics& g)
{
    //[UserPrePaint] Add your own custom painting code here..
    //[/UserPrePaint]
    g.fillAll (Colours::white);
    //[UserPaint] Add your own custom painting code here..
    //[/UserPaint]
}
void OperationPanel::resized()
{
    btn_OnOff->setBounds (19, 23, 65, 24);
    btn_InOut->setBounds (19, 52, 68, 24);
    OperationPanelGroup->setBounds (4, 4, 136, 92);
    //[UserResized] Add your own custom resize handling here..
    setBounds(4,4, 140,96);
    //[/UserResized]
}
 

 The ControlPanel file, is also build in the same way as above.