At the moment juce::CallOutBox::launchAsynchronously() launches an instance of juce::CallOutBox and it is almost impossible and at least impractical to launch a subclass of juce::CallOutBox for multiple reasons:
Internally juce::CallOutBox::launchAsynchronously() uses a juce::CallOutBoxCallback (which is private) which uses juce::CallOutBox as member variable. This cannot be changed from outside.
juce::CallOutBoxCallback relies on private helpers: detail::WindowingHelpers::isForegroundOrEmbeddedProcess()
It would be fantastic if juce::CallOutBox::launchAsynchronously() could be made a template function so we can pass the type of a subclass of juce::CallOutBox so that we can override and adjust the callout.
The reason for wanting to override juce::CallOutBox is the need to store state within the instance (in my case dropshadow state).
What exactly is your state? There is a hack where you can store something in a juce::ReferenceCountedObject and then store that in a juce::var as a property of your component (juce::Component::getProperties()).
But as your container will be created by you, can you just store the state in there as a member?
Iām trying to draw a custom background and outline for the CallOutBox which involves a drop shadow (melatonin::DropShadow). The problem basically is that I donāt have access to the dropshadow from within juce::LookAndFeelMethods::drawCallOutBoxBackground(), although your suggestion might help with this by attaching an instance of the dropshadow as property to the CallOutBox. The only thing Iād need is to figure out is when/how to resize the drop shadow because normally this happens inside the resized() method (which I canāt override). Maybe I can work around this by storing the previous size, and only update the size when it changed.
Also, would it hurt performance to get the type-erased reference counted instance of the drop shadow as property on each paint?
But as your container will be created by you, can you just store the state in there as a member?
Iām not sure what you mean bij ācontainerā, could you elaborate please?
I solved this problem by creating my own variant of CallOutBoxCallback which launches my subclass. This classes uses the private detail::WindowingHelpers which seems to be working fine, although I donāt like using private code de to potential breaking changes.
Your suggestions might also work, but the point of my feature request is to avoid these kinds of workarounds. I think the change in JUCE would be not that big and would give a lot of freedom and flexibility.
The CallOutBox does have a drop shadow out of the box!
btw. Iām using the CallOutBox as some sort of alternative to PopupMenu because of the rounded corners.
I just went through that last week in order to subclass CallOutBox! I did the same thing of creating my own CallOutBoxCallback - mostly that meant duplicating and renaming the existing JUCE version.
However, I wasnāt able to access the detail::WindowingHelpers::isForegroundOrEmbeddedProcess part ā how did you do that? I wound up just substituting a call to Process::isForegroundProcess(), which isnāt as comprehensive a check.
You can access those private facilities by including juce_gui_basics/detail/juce_WindowingHelpers.h directly. However, be careful because this header file doesnāt have any include guards. Also, since itās a private api it might break at some point.
To solve the include guard problem and to communicate clearly to myself and other developers what is going here Iāve encapsulated the logic in a separate header, like this:
#pragma once
#include <juce_gui_basics/juce_gui_basics.h>
// Note: it is bad practice to include private JUCE headers, but this gives us a workaround to make the WindowingHelpers
// available to client code.
#include "juce_gui_basics/detail/juce_WindowingHelpers.h"
/**
* This is a workaround to make WindowHelpers from juce available to client code. It is used to customize the
* juce::CalloutBox but put here in a spearate header to avoid double includes.
*/
struct WindowingHelpers
{
WindowingHelpers() = delete;
static juce::Image createIconForFile (const juce::File& file)
{
return juce::detail::WindowingHelpers::createIconForFile (file);
}
static bool isEmbeddedInForegroundProcess (juce::Component* c)
{
return juce::detail::WindowingHelpers::isEmbeddedInForegroundProcess (c);
}
static bool isWindowOnCurrentVirtualDesktop (void* ptr)
{
return juce::detail::WindowingHelpers::isWindowOnCurrentVirtualDesktop (ptr);
}
static bool isForegroundOrEmbeddedProcess (juce::Component* viewComponent)
{
return juce::detail::WindowingHelpers::isForegroundOrEmbeddedProcess (viewComponent);
}
template <typename Value>
static juce::BorderSize<int> roundToInt (juce::BorderSize<Value> border)
{
return juce::detail::WindowingHelpers::roundToInt (border);
}
};
Ah, I see, you had to directly include it. Yes, Iād like to avoid that in this case - thanks for explaining!
As a followup question, Iām wondering about the approach I took to replace that isForegroundOrEmbeddedProcess call with an equivalent check. The private JUCE method looks like this:
In my version of CallOutBoxCallback, I replaced the call to isForegroundOrEmbeddedProcess with just a call to Process::isForegroundProcess(). So that substitution leaves out the isEmbeddedInForegroundProcess part.
On macOS, isEmbeddedInForegroundProcess always returns false, so thereās no difference with my substitution on macOS.
But on Windows, isEmbeddedInForegroundProcess says it would return true āif the viewComponent is embedded into a window owned by the foreground process.ā I looked at that code, but Iām not clear on when that would return true. Would it maybe return true for a plugin in a DAW?
In this case, Iām using it in an application, not a plugin. So I think when itās the focused app, it would always be the foreground process, rather than be embedded in a foreground process. Iāve looked for edge cases and havenāt found them yet in this application⦠crossing fingers.