LookAndFeel for AlertWindows not working

Hi everyone

I am using a lot on custom look and feels in my project for things like sliders, comboboxes etc. and all is good but i tried to use a custom lookAndFeel on my alertWindow which doesn’t seem to work, i am wondering what am i doing wrong. Here is a snippet of my code that creates the alertWindow

// PluginEditor.cpp

void myAudioProcessorEditor::myAlertWindow()
{
AlertWindowLookAndFeel alertWindowLookAndFeel;

AlertWindow::showMessageBoxAsync(AlertWindow::NoIcon,
"Title",
"this is an alert window",
"ok",
this,
ModalCallbackFunction::create([this](int result) {}));


setLookAndFeel(&alertWindowLookAndFeel);

}

On my lookAndFeel class, i copied most on the juce code and modified it. I need to changes things like the background colour of the alertWindow and corner sizes.

AlertWindow* AlertWindowLookAndFeel::createAlertWindow (const String& title, const String& message,
const String& button1, const String& button2, const String& button3,
MessageBoxIconType iconType,
int numButtons, Component* associatedComponent)
{
auto boundsOffset = 50;

auto* aw = LookAndFeel_V2::createAlertWindow (title, message, button1, button2, button3,
                                              iconType, numButtons, associatedComponent);

auto bounds = aw->getBounds();
bounds = bounds.withSizeKeepingCentre (bounds.getWidth() + boundsOffset, bounds.getHeight() + boundsOffset);
aw->setBounds (bounds);

for (auto* child : aw->getChildren())
    if (auto* button = dynamic_cast<TextButton*> (child))
        button->setBounds (button->getBounds() + Point<int> (25, 40));

return aw;

}

void AlertWindowLookAndFeel::drawAlertBox (Graphics& g, AlertWindow& alert,
const Rectangle& textArea, TextLayout& textLayout)
{
auto cornerSize = 4.0f;

g.setColour (Colours::red);
g.drawRoundedRectangle (alert.getLocalBounds().toFloat(), cornerSize, 2.0f);

auto bounds = alert.getLocalBounds().reduced (1);
g.reduceClipRegion (bounds);

g.setColour (Colours::blue);
g.fillRoundedRectangle (bounds.toFloat(), cornerSize);

auto iconSpaceUsed = 0;

auto iconWidth = 80;
auto iconSize = jmin (iconWidth + 50, bounds.getHeight() + 20);

if (alert.containsAnyExtraComponents() || alert.getNumButtons() > 2)
    iconSize = jmin (iconSize, textArea.getHeight() + 50);

Rectangle<int> iconRect (iconSize / -10, iconSize / -10,
                         iconSize, iconSize);

if (alert.getAlertType() != MessageBoxIconType::NoIcon)
{
    Path icon;
    char character;
    uint32 colour;

    if (alert.getAlertType() == MessageBoxIconType::WarningIcon)
    {
        character = '!';

        icon.addTriangle ((float) iconRect.getX() + (float) iconRect.getWidth() * 0.5f, (float) iconRect.getY(),
                          static_cast<float> (iconRect.getRight()), static_cast<float> (iconRect.getBottom()),
                          static_cast<float> (iconRect.getX()), static_cast<float> (iconRect.getBottom()));

        icon = icon.createPathWithRoundedCorners (5.0f);
        colour = 0x66ff2a00;
    }
    else
    {
        colour = Colour (0xff00b0b9).withAlpha (0.4f).getARGB();
        character = alert.getAlertType() == MessageBoxIconType::InfoIcon ? 'i' : '?';

        icon.addEllipse (iconRect.toFloat());
    }

    GlyphArrangement ga;
    ga.addFittedText ({ (float) iconRect.getHeight() * 0.9f, Font::bold },
                      String::charToString ((juce_wchar) (uint8) character),
                      static_cast<float> (iconRect.getX()), static_cast<float> (iconRect.getY()),
                      static_cast<float> (iconRect.getWidth()), static_cast<float> (iconRect.getHeight()),
                      Justification::centred, false);
    ga.createPath (icon);

    icon.setUsingNonZeroWinding (false);
    g.setColour (Colour (colour));
    g.fillPath (icon);

    iconSpaceUsed = iconWidth;
}

g.setColour (Colours::green);

Rectangle<int> alertBounds (bounds.getX() + iconSpaceUsed, 30,
                            bounds.getWidth(), bounds.getHeight() - getAlertWindowButtonHeight() - 20);

textLayout.draw (g, alertBounds.toFloat());

}

What am i missing here.

Any help with this?

The drawAlertBox should change the corner size of my alert window and change the background colour. So since this is not working it means my lookandfeel is not updating my alertwindow, how can i solve this? It doesn’t seem like a lot needs to be done here but i just cant figure out what i’m doing wrong. Any help will be appreciated.

Assuming this is not pseudo-code, it seems you are declaring the LookAndFeel as a local variable? It will be out of scope by the time the Async Dialog launches. Make it a member variable.

Secondly, what are you assigning the LookAndFeel to? You would need a pointer to the actual AlertWindow - which hasn’t been created yet, since it’s Async.

You can sub-class AlertWindow, and in your inherited constructor, set the LookAndFeel. I’m not sure there’s another way to achieve it… That’s how I do it. :wink:

Thank you @stephenk for your response. I declared it that way since i am only assigning the LookAndFeel to this alertWindow, i thought this is acceptable unless i would be assigning the LookAndFeel to a number of alertWindows.

Im not sure what you mean by “You can sub-class AlertWindow, and in your inherited constructor, set the LookAndFeel”. Do you mean creating the a new class for my custom alertWindow that inherit from AlertWindow? Would you mind showing me an example.

You have to realize that launching anything Async means it “happens later”. Therefore, local variables will have been destroyed by the time the dialog executes.

Yes, I mean make a new class that inherits from Alert Window. Something like this (untested). You can then define your custom lookAndFeel inside the class, make it a member variable, and pass it to the dialog in the constructor.

class MyAlertWindow : public juce::AlertWindow
{
public:
    MyAlertWindow (const String& title,
                           const String& message,
                           MessageBoxIconType iconType,
                           MainComponent* comp)
    : AlertWindow(title, message, iconType, comp)
    {
        setLookAndFeel(&alertWindowLookAndFeel);
    }

private:
    class AlertWindowLookAndFeel : public juce::LookAndFeel_V2
    {
        // custom LAF stuff here
    }
    AlertWindowLookAndFeel alertWindowLookAndFeel;
};

Then, call it with your class:

{
    MyAlertWindow::showMessageBoxAsync(AlertWindow::NoIcon,
    "Title",
    "this is an alert window",
    "ok",
    this,
    ModalCallbackFunction::create([this](int result) {}));
}

Thanks @stephenk, don’t I need to override these LookAndFeel functions for them to work?

Yes, of course, you put them where it says “custom LAF stuff here”.

I’ve tried overriding but still my alertWindow’s background colour is not changing.

Here is the snippet of my code:

class MyAlertWindow : public AlertWindow
{
public:
    MyAlertWindow (const String& title,
                   const String& message,
                   MessageBoxIconType iconType,
                   Component* associatedComponent = nullptr)
    : AlertWindow(title, message, iconType, associatedComponent)
    {
        setLookAndFeel(&alertWindowLookAndFeel);
    }

private:
    class AlertWindowLookAndFeel : public LookAndFeel_V4
    {
        void drawAlertBox (Graphics& g, AlertWindow& alert, const Rectangle<int>& textArea, TextLayout& textLayout) override
        {
            auto cornerSize = 1.0f;
            
            g.setColour (Colours::black); // outline colour
            g.drawRoundedRectangle (alert.getLocalBounds().toFloat(), cornerSize, 2.0f);

            auto bounds = alert.getLocalBounds().reduced (1);
            g.reduceClipRegion (bounds);

            g.setColour (Colours::grey); // background colour
            g.fillRoundedRectangle (bounds.toFloat(), cornerSize);
        }
    };

    AlertWindowLookAndFeel alertWindowLookAndFeel;
};

Have you verified through debugging that your override of drawAlertBox() is being called?

Not really sure how to do that

You’re definitely going to need to use the Debugger if you want to do any serious work on development. What IDE are you using? Basically, you place a breakpoint inside the drawAlertBox() function, say on the first line, and then run the project. If/When the function gets called, your IDE will stop inside the debugger at that line of code and allow you to view the values of variables etc.

If the project is a plugin, you will need to either attach the debugger to your DAW, or the Juce Audio Plugin Host.

I looked into this more, including creating a project, and it would seem that this is impossible to do when you use the “Static Public Member Functions” to launch an AlertWindow. It will not call your inherited constructor.

I don’t use the AlertWindow that way myself; I don’t use the static functions, I use the regular constructor and create one into a std::unique_ptr, and then you have a pointer to it and you can do whatever you like to set the text, arguments, look and feel, etc.

Sorry for misleading you…

The only way to apply a LookAndFeel to the static methods is to use the defaultLookAndFeel, apparently:

Thank you @stephenk, no problem. I tried the std::unique_ptr approach. My first problem with that the alertWindow opens on the center of my desktop and not the center of the plugin. Secondly i couldn’t figure out how to set the LookAndFeel on that one too. Here is how i did it.

infoButton.onClick = [this]
    {
        /* infoWindow(); */
        alertWindow = std::make_unique<juce::AlertWindow>("Alert",
                                                          "Choose an option",
                                                          juce::MessageBoxIconType::NoIcon,
                                                          nullptr);
        alertWindow->showOkCancelBox(juce::MessageBoxIconType::NoIcon, "Title", "Message here.", "Ok", "Cancel", nullptr , ModalCallbackFunction::create([this](int result) {}));
        
//        setLookAndFeel(&alertWindowLookAndFeel);
        alertWindow->setLookAndFeel(&alertWindowLookAndFeel);
    }  ;

Yep, that’s one way. Is it working for you?

Why do you set the look and feel AFTER you’ve already shown the alert window?

@reFX :grin: I wasn’t aware that is what I’m doing, thank you for pointing that out. But setting it before crashes the plugin when I click the button that shows the alertwindow.

I meant before the call to “showOkCancelBox”, not before creating the pointer.

You mean like this?

infoButton.onClick = [this]
    {
        /* infoWindow(); */
        alertWindow = std::make_unique<juce::AlertWindow>("Alert",
                                                          "Choose an option",
                                                          juce::MessageBoxIconType::NoIcon,
                                                          nullptr);
//        setLookAndFeel(&alertWindowLookAndFeel);
        alertWindow->setLookAndFeel(&alertWindowLookAndFeel);

        alertWindow->showOkCancelBox(juce::MessageBoxIconType::NoIcon, "Title", "Message here.", "Ok", "Cancel", nullptr , ModalCallbackFunction::create([this](int result) {}));
        
    }  ;

It doesn’t work still. And the issue of the alertWindow showing is the center of the desktop instead of the center of the plugin how do you fix that?

I wouldn’t use the juce::AlertWindow class at all. The additional window often gets hidden behind other windows, especially in FL Studio. Then, you open a modal dialog that users can not see, and your plugin suddenly doesn’t react to anything anymore, confusing users.

We created our own ModalDialog class, which inherits from a standard juce::Component. Now, we can position it anywhere we want; it doesn’t block anything and can’t be accidentally hidden.

I see, I think i should try that too. Thank you.