Workflow - Juce and Design

Hi y’all, and I’m sorry if this is sort of a re-post. the latest post in the subject was from 2011…

I’m a graphic design student getting started with my graduation project - For those who do not know, it’s the last and largest project in our degree where we basically create our own brief and we have to design everything and get scored on the design.

My partner for this project is a programmer and a sound engineer. We are trying to create a “gateway drug” sort of synth for beginners to get into the world of making noise. The synth is divided into two parts: a dashboard for the user’s computer and a MIDI keyboard.

We were thinking about using Juce to create the audio experience, I’m trying to figure out a workflow so that I can take the framework my partner created using juce and build the UI myself so that we have more time for development and design and less time spent on frontend integration. I have experience with HTML CSS (and a bit of background with Python and C#) as well as being proficient in Unity as a tech artist and in Figma as a designer for UX and UI.

So, have you done anything like this and have some insights to share? Is there an easy or smart workflow that we as a team could use? I appreciate any help and tips you can give. :camera:

1 Like

JUCE offers a way of arranging its UI widgets via FlexBox. I don’t normally use it but AFAIK that’s the closest point you can get in JUCE to an arrangement that resembles what happens in CSS.

1 Like

Thanks, I’ll have a look at the two.
I was hoping there was somthing easier (for me) like a visual tool such as figma or unity I could use to create the UI. But I’ll keep looking at all the options

This kind of separation exists. The sound generation happens in the AudioProcessor, and the AudioProcessorEditor is the kind of dashboard, if I understand you correctly.

I have written a GUI engine, that is configured using a DOM of controls, that are layout either via FlexBox or proportionally. It also features a visual property map similar to old school CSS inheriting colours and properties from parents.
This is called PluginGuiMagic, maybe you want to try it out:

It is a juce module that you can add to your project, and with a few changes it will populate the plugin editor with the controls you define while the plugin is running live.

This is a step by step guide how to add PluginGuiMagic to your plugin:

2 Likes

One of the ongoing debates about doing GUI design in JUCE is whether or not to go the LookAndFeel route, or whether to subclass existing Components and override their paint() methods to customize the look of them that way. There are people who feel very strongly on both sides of that debate.

With the LookAndFeel approach, you have the benefit (like working with a CSS style sheet) of keeping the customized look of your app all in a central place. The downside is that it can get quite convoluted, remembering which LookAndFeel methods are being called, or untangling why the styles aren’t being applied as you would expect them to be.

For a one-off project, it might be simpler to go with subclassing Components and overriding paint().

If you do go the LookAndFeel route, this pair of helper functions I wrote might aid you. They provide a wrapper around one property of the NamedValueSet that each Component contains. Using setGuiClass in your Editor code allows you to tag a component with a class name, and then you can access that in the LookAndFeel class with getGuiClass, to style the Component appropriately.

FWIW what I like to do regarding widget rendering is to have one LnF class for every “kind” of widgets.

So, one for buttons, one for checkboxes, one for faders, one for knobs and so on.
If for some reason I need to have two different knob styles, each of those styles gets its LnF class.

It’s like each of my LookAndFeel classes represents one of those “GuiClass” concepts you mention in your code. For maintainability, I prefer to have two different small classes rather than a bigger one that needs to look up the value of a string in a big “if” to choose which rendering style to apply, but that’s just personal preference and your approach could be more appropriate for cases when the variations between different classes are minimal and the bulk of the rendering code is the same.

Yes, the big “if” statements could potentially be awkward in a LookAndFeel class, but since the LnF methods are already broken down by Component subclass (i.e. the methods for drawing Labels are different than methods for drawing TextButtons, etc), there’s usually only a few “styles” of each (that I would tag with the “GuiClass” concept) in a given project. So the “if” statements don’t get too out of hand.

I do wonder why, if you’re going to take the approach of managing a collection of custom widgets with a folder full of subclasses, that you wouldn’t just have them be subclassed widgets rather than subclassed LookAndFeels. It would seem to add an extra level of abstraction without benefit to use a bunch of subclassed LnFs. Most LnF drawing methods are just passing in a Graphics object and a couple other bits of data, so the drawing for those could instead be done right in a subclass’s paint method. (I guess the exception to that would be when the widget’s paint method is passing data from private members into the LnF, e.g. in the ComboBox::paint method.) Is there another benefit that you’re seeing from using lots of small LnF classes?

In my case, I find it particularly beneficial because the widget rendering is often done with bitmap resources.

By using LnF classes, I store the bitmap resources in the LnF class and they are automatically shared among all the widgets that make use of it, in contrast with each widget having to load its own copy of the bitmap resources to paint. That saves a lot of memory and some load time because the bitmaps only have to be loaded once per LnF.

Taking this one step further, my LnF classes are also shared between plug-in instances via singletons*, which increases the benefit in terms of occupied memory and load times.

*currently, this approach only applies when DAWs host all plug-in instances in the same process. That used to be the norm but the trend currently seems to slowly drift towards hosting plug-ins in separate processes, or at least to give the user the choiche to do so. In this latter case, some interprocess communication would be required to share resources among them and I haven’t gone that route yet, I’ve yet to determine the benefit/costs of that.

This helps probably your mental model, so it is a fair approach. However, image resources are shared via the ImageCache anyway…

I dislike having a LookAndFeel which handles only one component and is “useless” for all others.
In an ideal world I would have a LookAndFeel with my “house style”, but I rarely get to that point of completeness…

I’m not using ImageCache for reasons that are not relevant to the discussion here, however I agree that if one uses ImageCache, then the argument of memory and load times becomes invalid and it becomes only a matter of mental model.

That comes with the shortcoming that, if your UI calls for two different styles for the same widget, for example a big knob and a smaller one that are drawn fundamentally different because of the size, you have to work around that somehow.

Sure, one possibility is to resort to the “GuiClass” trick explained above, but that comes at the cost of a properties lookup and an additional “if” every time the widget is painted.

I’m wondering what technique people use to define a look and feel for custom widgets, for instance an ADSR envelope or an XY pad, widgets that are not part of JUCE but widgets that might require a custom look for different projects. It is not possible to “add” virtual methods to the JUCE look and feel base class. You could create a custom look and feel and add the virtual methods there but this is not very scaleable or composable.

We do something like this:

class MyComponent : public juce::Component
{
public:
    struct LookAndFeelMethods
    {
        virtual ~LookAndFeelMethods() = default;

        virtual void drawMyComponent (juce::Graphics&, MyComponent&) = 0;
    };

protected:
    void paint (juce::Graphics& g) override
    {
        if (auto* myLookAndFeel = dynamic_cast<LookAndFeelMethods*> (&getLookAndFeel()))
            myLookAndFeel->drawMyComponent (g, *this);
    }
};

I experimented a few times with this issue and ended up with somehting similar:

class MyComponent : public juce::Component
{
public:
    struct LookAndFeelMethods
    {
        virtual ~LookAndFeelMethods() = default;

        virtual void drawMyComponent (juce::Graphics&, MyComponent&) = 0;
    };

    struct LookAndFeelFallback : juce::LookAndFeel_V4, public LookAndFeelMethods
    {
        ~LookAndFeelFallback() override = default;

        virtual void drawMyComponent (juce::Graphics&, MyComponent&) 
        {
            // implementation or just
            g.fillAll (juce::Colours::red);
        }
    };

    void paint (juce::Graphics& g) override
    {
        myLookAndFeel->drawMyComponent (g, *this);
    }

    void lookAndFeelChanged() override
    {
        if (auto* lnf = dynamic_cast<LookAnfFeelMethods*>(&getLookAndFeel))
        {
            myLookAndFeel = lnf;
        }
        else
        {
            myLookAndFeel = &lnfFallback;
            jassertfalse; // depending if you want to allow the fallback or not
        }
    }

private:
    LookAndFeelFallback lnfFallback;
    LookAndFeelMethods* myLookAndFeel=&lnfFallback;
};

The fallback allows to have the dynamic_cast only once and you can use the pointer unchecked.

The approach has still some drawbacks, so you cannot use findColour() in the LookAndFeelMethods.

I think I use similar approach:

    void lookAndFeelChanged() override
    {
        if (auto* laf = dynamic_cast<LookAndFeelMethods*>(&getLookAndFeel()))
            currentLookAndFeel = laf;
        else
            currentLookAndFeel = &defaultLookAndFeel;
    };
LookAndFeelMethods* currentLookAndFeel { &defaultLookAndFeel };
LookAndFeelMethods  defaultLookAndFeel;

However, I am not satisfied with this sometimes. It is problematic to have templated components using LookAndFeel, using subclassed components requires some tricks to draw everything as expected, etc. I would like to have something maybe like paint delegates that can be managed by some LookAndFeel manager?