[PATCH] Path::addBubble

As the title does not say, there is a bug in Path addBubble when the bubble’s tip is not on the bottom, because the code create rectangle with negative width, and Rectangle::contains complains with them.

The following code fixes it:

void Path::addBubble (const Rectangle<float>& bodyArea,
                      const Rectangle<float>& maximumArea,
                      const Point<float>& arrowTip,
                      const float cornerSize,
                      const float arrowBaseWidth)
{
    const float cornerSize2 = 2.0f * cornerSize;

    startNewSubPath (bodyArea.getX() + cornerSize, bodyArea.getY());


    float targetLimitX = bodyArea.getX() + cornerSize + arrowBaseWidth;
    float targetLimitW = bodyArea.getWidth() - cornerSize2 - arrowBaseWidth * 2.0f;
 +   if(targetLimitW < 0)
 +   {
 +       targetLimitX = targetLimitX + targetLimitW;
 +       targetLimitW = -targetLimitW;
 +   }

    float targetLimitY = bodyArea.getY() + cornerSize + arrowBaseWidth;
    float targetLimitH = bodyArea.getHeight() - cornerSize2 - arrowBaseWidth * 2.0f;
 +   if(targetLimitH < 0)
 +   {
 +       targetLimitY = targetLimitY + targetLimitH;
 +       targetLimitH = -targetLimitH;
 +   }

     if (Rectangle<float> (targetLimitX, maximumArea.getY(),
                          targetLimitW, bodyArea.getY() - maximumArea.getY()).contains (arrowTip))
    {
        lineTo (arrowTip.x - arrowBaseWidth, bodyArea.getY());
        lineTo (arrowTip.x, arrowTip.y);
        lineTo (arrowTip.x + arrowBaseWidth, bodyArea.getY());
    }

    lineTo (bodyArea.getRight() - cornerSize, bodyArea.getY());
    addArc (bodyArea.getRight() - cornerSize2, bodyArea.getY(), cornerSize2, cornerSize2, 0, float_Pi * 0.5f);

    if (Rectangle<float> (bodyArea.getRight(), targetLimitY,
                          maximumArea.getRight() - bodyArea.getRight(), targetLimitH).contains (arrowTip))
    {
        lineTo (bodyArea.getRight(), arrowTip.y - arrowBaseWidth);
        lineTo (arrowTip.x, arrowTip.y);
        lineTo (bodyArea.getRight(), arrowTip.y + arrowBaseWidth);
    }

    lineTo (bodyArea.getRight(), bodyArea.getBottom() - cornerSize);
    addArc (bodyArea.getRight() - cornerSize2, bodyArea.getBottom() - cornerSize2, cornerSize2, cornerSize2, float_Pi * 0.5f, float_Pi);

    if (Rectangle<float> (targetLimitX, bodyArea.getBottom(),
                          targetLimitW, maximumArea.getBottom() - bodyArea.getBottom()).contains (arrowTip))
    {
        lineTo (arrowTip.x + arrowBaseWidth, bodyArea.getBottom());
        lineTo (arrowTip.x, arrowTip.y);
        lineTo (arrowTip.x - arrowBaseWidth, bodyArea.getBottom());
    }

    lineTo (bodyArea.getX() + cornerSize, bodyArea.getBottom());
    addArc (bodyArea.getX(), bodyArea.getBottom() - cornerSize2, cornerSize2, cornerSize2, float_Pi, float_Pi * 1.5f);

    if (Rectangle<float> (maximumArea.getX(), targetLimitY, bodyArea.getX() - maximumArea.getX(), targetLimitH).contains (arrowTip))
    {
        lineTo (bodyArea.getX(), arrowTip.y + arrowBaseWidth);
        lineTo (arrowTip.x, arrowTip.y);
        lineTo (bodyArea.getX(), arrowTip.y - arrowBaseWidth);
    }

    lineTo (bodyArea.getX(), bodyArea.getY() + cornerSize);
    addArc (bodyArea.getX(), bodyArea.getY(), cornerSize2, cornerSize2, float_Pi * 1.5f, float_Pi * 2.0f - 0.05f);

    closeSubPath();
}

// Example code that's failing without the change:
    Path  path;
    juce::Rectangle<float> bodyAreaRect(0, 0, static_cast<float>(getWidth()),  static_cast<float>(getHeight() / 2));
    juce::Rectangle<float> maxAreaRect(0, 0, static_cast<float>(getWidth()),  static_cast<float>(getHeight()));
    juce::Point<float> tipPosPoint(static_cast<float>(getWidth() / 2) ,  static_cast<float>(getHeight() - 1));

    float cornerSize = static_cast<float>(getWidth() / 3);
    float arrowBaseWidth = static_cast<float>(getWidth() / 4);
    path.addBubble(bodyAreaRect, maxAreaRect, tipPosPoint, cornerSize, arrowBaseWidth);

    g.setColour(backgroundColor);
    g.fillPath(path);
    g.setColour(edgeColor);
    PathStrokeType type(1);
    g.strokePath(path, type);

Thanks, I’ll sort that out.