Tooltips (edit - actually BubbleMessageComponent) clipped off the edge of plugin window

Can somebody remind me how to prevent tooltips getting clipped off the edge of the plugin window (see image - this is the left edge of my plugin with Ableton behind) - or confirm if that’s a bug maybe? - I thought that JUCE tooltip was supposed to re-orient the position/direction automatically based on the available space to display it.
(JUCE 7.0.10 - testing in Windows VST3)

image

From the TooltipWindow class docs:

“For audio plug-ins (which should not be opening native windows) it is better to add a TooltipWindow as a member variable to the editor and ensure that the editor is the parentComponent of your TooltipWindow. This will ensure that your TooltipWindow is scaled according to your editor and the DAWs scaling setting.”

I tried to see if I’m doing anything special other than that and it appears not…

While it doesn’t say anything about keeping it inside the parent, I would think that that would be part of it.

Caveat: I’m still running JUCE 7.0.9.

PS. Took another look. You might want to look at what your LookAndFeel::getTooltipBounds() is doing. There’s a line at the end that constrains it to the parent component.

//==============================================================================
Rectangle<int> LookAndFeel_V2::getTooltipBounds (const String& tipText, Point<int> screenPos, Rectangle<int> parentArea)
{
    const TextLayout tl (detail::LookAndFeelHelpers::layoutTooltipText (tipText, Colours::black));

    auto w = (int) (tl.getWidth() + 14.0f);
    auto h = (int) (tl.getHeight() + 6.0f);

    return Rectangle<int> (screenPos.x > parentArea.getCentreX() ? screenPos.x - (w + 12) : screenPos.x + 24,
                           screenPos.y > parentArea.getCentreY() ? screenPos.y - (h + 6)  : screenPos.y + 6,
                           w, h)
             .constrainedWithin (parentArea);
}

https://docs.juce.com/master/classTooltipWindow.html#aba10e88ecad73cfa0f0a21df1c9f9e51

What did you set as the parent?

You should create a single SharedResourcePointer of TooltipWindow in your processor class.

Rail

1 Like

That looks like it’s a BubbleMessageComponent rather than a TooltipComponent. Make sure you’re using the AudioProcessorEditor, or another Component with the same bounds, as the parent of the BubbleMessageComponent, and make sure that you’re not restricting the valid placements with setAllowedPlacement().

1 Like

Sorry folks about the confusion. You are right @reuk - it’s been such a long time since I looked at my implementation I forgot that I’m actually using a different method to get the tooltip text from components by checking for component under mouse and showing a BubbleMessageComponent.

Thanks for the tip on checking for usage of setAllowedPlacement, for most situations it looks like I’m allowing placement in all directions (some specific cases I limit it to above or below) - it’s possible I’ve not forced the parent of the Bubble message to be the top level editor… will check that.

If you want it to extend outside the bounds of your UI you need to make the parent the Desktop

Rail

I checked that the component is a child of the editor window, and that I’m allowing all placement options, but it seems that there’s no bounds check when using ‘showAt’ with a target component that’s a child (of a child of a child) of the editor:

Ah… just missed your message… yes - I can try that, although it might be better if I can get the message to display to the right of the component if detected it goes out of bounds.

Really odd, because when the target component is a knob rather than a button the same bubble message code is hit (with all the placement options) and yet the direction of the bubble is properly displayed according to available editor window space - here this is on the right hand side of my plugin window:
image

yet, showAt on a button component doesn’t work as expected:

image

Will have to dig a bit more.

Do your buttons and Sliders possibly have different LnFs ?

Rail

As you suggested, I’d also recommend avoiding additional desktop windows in plugins. Displaying the entire UI inside the main editor window will have the most predictable behaviour, especially if you want to target AUv3.

1 Like

Yes they have custom LnF, but I’m not clear on why it would affect bubble message placement - since I only override the drawing of the slider/buttons, and not the BubbleMessage.

Other than for a specific component that’s drawn in a view port, I just have a single bubble message class instance (that contains the bubble message component) that’s owned by the editor and hidden or shown if it detects it’s over a component that has a tooltip.

Slider has:

int LookAndFeel_V2::getSliderPopupPlacement (Slider&)
{
return BubbleComponent::above
| BubbleComponent::below
| BubbleComponent::left
| BubbleComponent::right;
}

Buttons don’t have BubbleComponents so you’d have to handle that yourself in the LookAndFeel_V2::drawBubble()

Perhaps study the Slider popup window implementation (PopupDisplayComponent) to see how they handle the BubbleComponent.

Rail

1 Like

Will check, although still a bit confused - the bubble message I’m displaying is owned by my plugin editor, and not owned by the sliders or buttons (it’s only drawing the text by recognising the component under the mouse is a tool tip client - and then requesting the text) - and in fact none of the sliders pop up related code is entered when my editor bubble message is presented.

BTW - sorry for the confusion (my issue is BubbleMessageComponent related) and thanks for your input anyway.

It seems that the BubbleComponent set position code just ignores the width of the bubble to be placed (vs available space to the left or right) if it sees the target rectangle/target component size is wider than it is high, this is why my checkbox button component is getting the bubble placed under it (but clipped off the edge of the editor window), but the slider/knob (which is a square area) is getting the bubble placed to the left or right if it would not fit horizontally in the editor window:

void BubbleComponent::setPosition (Rectangle<int> rectangleToPointTo,
                                   int distanceFromTarget, int arrowLength)
{
    {
        int contentW = 150, contentH = 30;
        getContentSize (contentW, contentH);
        content.setBounds (distanceFromTarget, distanceFromTarget, contentW, contentH);
    }

    const int totalW = content.getWidth()  + distanceFromTarget * 2;
    const int totalH = content.getHeight() + distanceFromTarget * 2;

    auto availableSpace = (getParentComponent() != nullptr ? getParentComponent()->getLocalBounds()
                                                           : getParentMonitorArea().transformedBy (getTransform().inverted()));

    int spaceAbove = ((allowablePlacements & above) != 0) ? jmax (0, rectangleToPointTo.getY()  - availableSpace.getY()) : -1;
    int spaceBelow = ((allowablePlacements & below) != 0) ? jmax (0, availableSpace.getBottom() - rectangleToPointTo.getBottom()) : -1;
    int spaceLeft  = ((allowablePlacements & left)  != 0) ? jmax (0, rectangleToPointTo.getX()  - availableSpace.getX()) : -1;
    int spaceRight = ((allowablePlacements & right) != 0) ? jmax (0, availableSpace.getRight()  - rectangleToPointTo.getRight()) : -1;

    **// look at whether the component is elongated, and if so, try to position next to its longer dimension.**
    if (rectangleToPointTo.getWidth() > rectangleToPointTo.getHeight() * 2
         && (spaceAbove > totalH + 20 || spaceBelow > totalH + 20))
    {
        **spaceLeft = spaceRight = 0;**
    }
    else if (rectangleToPointTo.getWidth() < rectangleToPointTo.getHeight() / 2
              && (spaceLeft > totalW + 20 || spaceRight > totalW + 20))
    {
        spaceAbove = spaceBelow = 0;
    }

    int targetX, targetY;

    if (jmax (spaceAbove, spaceBelow) >= jmax (spaceLeft, spaceRight))
    {
        targetX = rectangleToPointTo.getCentre().x;
        arrowTip.x = totalW / 2;

Just FYI, whilst there is no doubt a way to improve the BubbleComponent:setPosition function to avoid drawing a bubble outside to the available space, my quick hack is to avoid any changes there and just make sure to pass in a square area for where I want to show a bubble message, and specifically in my UI I can know that all my toggle buttons have text to the right, and I want to show bubble messages to the left of the checkbox only, so I will check for that (and still make the target area a square).
These changes fix the issue I had with the message getting clipped outside of the editor window:

        // get point relative to editor
        juce::Point<int> referencePos;
        // if (auto* editor = findParentComponentOfClass<juce::AudioProcessorEditor>()) 
        // in my case I know this bubble message handler class is the child of the editor - so no need to search
        juce::AudioProcessorEditor* editor = static_cast<juce::AudioProcessorEditor*>(this->getParentComponent());
        {
            auto localPoint = underMouse->getLocalPoint(editor, juce::Point<int>(0, 0));
            referencePos = { -localPoint.getX(), -localPoint.getY() };
        }

        juce::Rectangle<int> target{ referencePos.getX(),referencePos.getY(), underMouse->getLocalBounds().getWidth(),underMouse->getLocalBounds().getHeight() };


        int targetWidth = target.getWidth();
        int targetHeight = target.getHeight();

        if (ToggleButton* component = dynamic_cast<ToggleButton*>(underMouse))
        {
            target.setWidth(targetHeight);
            bubbleMessage.setAllowedPlacement(BubbleMessageComponent::left);
        }
        else
        {
            int size = std::max(targetWidth, targetHeight);

            if (targetWidth > (2 * targetHeight))
            {
                size = std::max((int)(targetWidth * 0.75f), targetHeight);
            }
            else if (targetHeight > (2 * targetWidth))
            {
                size = std::max(targetWidth, (int)(targetHeight * 0.75f));
            }

            target = target.withSizeKeepingCentre(size, size);
        }

        bubbleMessage.showAt(target, text, 4000, true, false);