Adding a DrawableButton component

Hey there,

Struggling a little bit, as I’m trying to add the juce::DrawableButton component to PGM, I’l facing two issues:

  • how do I make sure it’ll be properly registered (I’m following th examples provided so I should get this alright unless there’s something specific)
  • how do I make it work, as this button is not speficically tied to a parameter but should trigger something (a method that could be passed at init ? and get back to its inactivated state ?)

Any help would be greatly appreciated.
P.S. I’m not a noob at JUCE anymore but far from an experienced user as well :wink:

Hi and Welcome!

Great suggestion, that should be fairly straight forward.
To learn how to do it, best look into foleys_gui_magic/General/foleys_MagicJUCEFactories.cpp

You inherit foleys::GuiItem which will have the component you want to wrap as member.

In the public section of your GuiItem derrived class add the macro:

FOLEYS_DECLARE_GUI_FACTORY (MyItemName)

This will create a factory method so you can add the item to the MagicBuilder.

If this is in a plugin, there is a method in MagicProcessor you should override:

void ExtendingExampleAudioProcessor::initialiseBuilder (foleys::MagicGUIBuilder& builder)
{
    builder.registerJUCEFactories();
    builder.registerJUCELookAndFeels();
    
    builder.registerFactory ("Lissajour", &LissajourItem::factory);
    builder.registerFactory ("Statistics", &StatisticsComponentItem::factory);
}

This is taken from Examples/ExtendingExample.

The MagicState serves as multi connector from the GUI to the processor. You can expose all kind of things, the AudioProcessorParameters are only one thing.
Additionally you can add objects. Only restriction is, that it is owned by the magicState:

MyCustomObject* object = magicState.createAndAddObject<MyCustomObject>("identifier", CreationArgs&&);
// and retrieve it:
auto* object = magicState.getObjectOfType<MyCustomObject>("identifier");

And last but not least you can add lambdas to be connected from the GUI:

magicState.addTrigger("myCustomAction",
[this]()
{
    doWhatever();
});

You can see this in action in the TextButton in foleys_MagicJUCEFactories.cpp:

In case it wasn’t obvious: the update() callback is called when constructing the GUI from the ValueTree or whenever the ValueTree changes (during editing, but also if you create programmatic GUIs.

Let me know if that helps or if you need further details.

Hello Daniel,
Took me a while to get back to you on this topic and figure out what to do next. I’ve tried to follow your example, however I don’t see in the ExtendingExample how your Lissajour is used, as it is not instantiated, it doen’t seem to be used anywhere ? BTW I have missing symbols when trying to build that project on XCode…

Do you happen to have a clue ? Or maybe another example app that I could check against ?

Thanks,

The class is not used in the code, that is right. Because the PGM module doen’t know your class (and the Lissajour class is an example of such an unknown class).
But when you added the class to the builder in the YourAudioProcessor::initialiseBuilder() callback, you registered a factory.
When a node called Lissajour shows up in the magic.xml, then the factory is called and a LissajourItem is constructed, which contains your Lissajour object.

builder.registerFactory ("Lissajour", &LissajourItem::factory);

And in the xml:

    <View>
      <Lissajour lissajour-draw="FF141272" factor="5"/>
      <Lissajour lissajour-draw="red" lissajour-fill="70ff0000"/>
    </View>

For the missing symbols it would help to know, which ones. Chances are, you activated FOLEYS_ENABLE_BINARY_DATA but did not add binary data to your project, in which case the BinaryData callbacks are not implemented byt the juce setup.
But that is a wild guess, could be anything. I can only tell when I know which symbols.

1 Like

Actually you’re right, I had elements used in my Look And Feel that I included in your example, which were inside another binarydata, sorted that out now I think.
Now I have a compilation error when adding my guielement on the constructor, I don’t know what it’s expecting:

DrawableButtonPGM (foleys::MagicGUIBuilder& builder, const juce::ValueTree& node)

What should be these when adding that element using:
magicState.createAndAddObject<AoW::DrawableButtonPGM>(“Drawable”);
?
Thanks :smiley:

I don’t know what you are doing, but to add a class to the builder, it needs to declare the factory. I added a macro so you don’t have to type the factory method manually:

This line goes in the public section of your item.
And the Item must inherit GuiItem.

The actual component you want to see is INSIDE that item, because the item implements all the decorators and configuration from the builder.

Hope that helps

That’s what I did.

However, as it had been a while since I had used PGM, I was trying to add the graphic components myself, where it’s not needed, only creating attachment from them to my parameters using the treestate.

Now it builds fine, thanks. I need to check how I can make my drawable button to trigger a function when clicked. I did this:
magicState.addTrigger(“NEWSEED”,
[this]()
{
DBG(“Clicked”);
});

Not sure what “NEWSEED” should contain (the id of the parameter declared which I attached like so ?
newseedAttachment(treeState, newseed, “NEWSEED”, nullptr),

Cheers,

Have a look how TextButtonItem does it:

I’ve seen that, the trouble is that I don’t get how I’m supposed to use that thing to declare a trigger, again I can’t find an example of this being used in a “real life” situation…

Ok, so basically every GuiItem has access to the magicState and the config node the designer can edit.

To add a property in the config, where the designer can select a trigger, you need to add the properties in the callback:

std::vector<SettableProperty> getSettableProperties() const override
{
    std::vector<SettableProperty> props;
    props.push_back ({ configNode, "trigger-name", SettableProperty::Choice, {}, magicBuilder.createTriggerMenuLambda() });
    return props;

“trigger-name” will be the property to edit. It will show a selection of all triggers that were added to the magicState.

In the GuiItem update() callback, you can retrieve the trigger aka lambda and connect it to your wrapped button:

    auto triggerID = getProperty (pOnClick).toString();
    triggerToCall = triggerID.isNotEmpty() ? getMagicState().getTrigger (triggerID) : nullptr;

    button.onClick = triggerToCall;

Now clicking the button will call the trigger.

1 Like

OK ! That was the missing link, that the connection between a guiitem and the trigger was to be made using the node designer, I thought somehow it had to be done through the code !
Working now, thanks :smiley:

Yeah, the goal is that the developer is not needed to assemble and style the GUI. But that a developer can add specific items for their internal use case.

Not sure it has any link with PGM, but any idea why the plugins takes 100% cpu when I reopen its window? doesn’t happen at first load :confused:

hmm, they don’t here… very weird. Can you profile that?

UI can be resized but is unresponsive otherwise, unclear… I’ll try to put that into profiler, seems to calm down after a while…

that sounds really bad

There seems to be an atomic locking somewhere, heaviest track is
juce::ReferenceCountedArray<juce::MessageManager::MessageBase, juce::CriticalSection>::removeAndReturn(int)
Maybe I’m using std::atomic wrongly to handle my parameters ?

yeah, that doesn’t ring a bell, sorry

Most likely some atomics I was using direclty without passing through their load() method… Will report back if there’s a change (some questions probably meant for the JUCE channel in the audioprogrammer discord^^)