How to draw an incomplete rounded rectangle?

I want to draw an incomplete rounded rectangle using juce::Graphics, something like these:

Notice that in the second one, the split comes on the right side, just before the rounded edges are complete. Think of it like the shaded section of a slider, which will be complete or incomplete depending on the level of the slider.

Any ideas how to do this? I’ve looked through the usual draw functions and it seems not so simple.

I think reduceClipRegion() is what you need here maybe?
Alternatively you could try experimenting with Drawables and setClipPath().
The other option would be to create a child component for this slider that draws a rounded rectangle of a set size, and then dynamically change its bounds to clip the drawing of the rounded rectangle.
Those are the things I’d experiment with anyway.

See Path::addRoundedRectangle

addRoundedRectangle() [3/5]

void juce::Path::addRoundedRectangle ( float x,
float y,
float width,
float height,
float cornerSizeX,
float cornerSizeY,
bool curveTopLeft,
bool curveTopRight,
bool curveBottomLeft,
bool curveBottomRight )

Adds a rectangle with rounded corners to the path.

The rectangle is added as a new sub-path. (Any currently open paths will be left open).

See also

addRectangle, addTriangle

References x, and y.

Rail

For exactly that kind of thing I have been using this and it works great:

Here’s a little wrapper I use:

#pragma once
#include <JuceHeader.h>
#include "clipper.h"

// basic JUCE wrapper for the Clip2Lib library
// note: only suitable for closed paths

inline Clipper2Lib::PathsD jucePathToClipperPaths (const juce::Path& jucePath)
{
    Clipper2Lib::PathsD clipperPaths;
    std::vector<double> shape;
    juce::PathFlatteningIterator iterator (jucePath);

    while (iterator.next())
    {
        shape.push_back (iterator.x1);
        shape.push_back (iterator.y1);
        shape.push_back (iterator.x2);
        shape.push_back (iterator.y2);   
    
        if (iterator.closesSubPath)
        {
            clipperPaths.push_back (Clipper2Lib::MakePathD (shape));
            shape.clear();
        }
    }
    
    return clipperPaths;
}

inline juce::Path clipperPathsToJucePath (const Clipper2Lib::PathsD& clipperPaths)
{
    juce::Path jucePath;
    
    for (auto& clipperPath : clipperPaths)
    {
        const auto numPoints = clipperPath.size();
        
        if (numPoints >= 2)
        {
            const auto& start = clipperPath[0];
            
            jucePath.startNewSubPath (static_cast<float>(start.x),
                                      static_cast<float>(start.y));

            for (int i = 1; i < numPoints; ++i)
            {
                const auto& point = clipperPath[i];
                jucePath.lineTo (static_cast<float>(point.x),
                                 static_cast<float>(point.y));
            }

            const auto& last = clipperPath[numPoints - 1];
            jucePath.lineTo (static_cast<float>(last.x),
                             static_cast<float>(last.y));

            jucePath.closeSubPath();
        }
    }   
    
    return jucePath;
}

inline juce::Path intersectPaths (const juce::Path& jucePathA,
                                  const juce::Path& jucePathB,
                                  int precision = 3)
{
    return clipperPathsToJucePath (Clipper2Lib::Intersect (jucePathToClipperPaths (jucePathA), 
                                                           jucePathToClipperPaths (jucePathB), 
                                                           Clipper2Lib::FillRule::NonZero, 
                                                           precision));    
}

inline juce::Path addPaths (const juce::Path& jucePathA,
                            const juce::Path& jucePathB,
                            int precision = 3)
{
    return clipperPathsToJucePath (Clipper2Lib::Union (jucePathToClipperPaths (jucePathA), 
                                                       jucePathToClipperPaths (jucePathB), 
                                                       Clipper2Lib::FillRule::NonZero, 
                                                       precision)); 
}

inline juce::Path substractPaths (const juce::Path& jucePathA,
                                  const juce::Path& jucePathB,
                                  int precision = 3)
{
    return clipperPathsToJucePath (Clipper2Lib::Difference (jucePathToClipperPaths (jucePathA), 
                                                            jucePathToClipperPaths (jucePathB), 
                                                            Clipper2Lib::FillRule::NonZero, 
                                                            precision)); 
}

inline juce::Path xorPaths (const juce::Path& jucePathA,
                            const juce::Path& jucePathB,
                            int precision = 3)
{
    return clipperPathsToJucePath (Clipper2Lib::Xor (jucePathToClipperPaths (jucePathA), 
                                                     jucePathToClipperPaths (jucePathB), 
                                                     Clipper2Lib::FillRule::NonZero, 
                                                     precision)); 
}

1 Like

Thanks folks, I’ll give these ideas a shot! @railjonrogut I didn’t notice that I could give different sizes to different corners. I suppose this won’t give exactly the effect I was describing, but probably something pretty close. If I don’t like the look of it, I’ll use the external library that @aamf suggests.

@icebreakeraudio, I wasn’t able to get reduceClipRegion() to work for doing this, though admittedly I don’t understand it very well.

You can set the corner values the same but turn each corner on or off which is exactly what you want

Rail

Right. I’m guessing it won’t do the incomplete corner thing that I was asking about, like when the slider gets close to the edge, but I think I can live with that.

You first create a path with the normal rounded corners for the entire progress bar.

You call reduceClipRegion with that path as the only parameter.

All future draw commands will now be limited to that path, so you can draw your progress bar with regular rectangles (not rounded ones) and the clip region will mask out the corners, etc.

2 Likes

If you want different size corners you can create 4 quadrants (break the rectangle into 4 pieces) in your path and then fill them