Rounded corners Component

I’d like to share a reusable RoundedCornersEffect class derived from juce::ImageFilterEffect

To use:

  1. Add a RoundedCornersEffect private member variable to your Component derived class.
  2. Set the corner radius using setCornerRadius either in the constructor, or in resized() where you can pass it a corner radius and it will apply the rounded corners based on the bounds of the image of the Component to be painted.
  3. Alternatively, provide a rounded Rectangle clip reduction Path using setClipReductionPath
  4. setComponentEffect (&roundedCornersEffect) in the constructor or prior in a setter or prior to paint()

In a reusable Component such as a class derived from DrawableButton, its good to check the corner size set is > 0 to avoid setting the effect unnecessarily.

4 Likes

#pragma once

class RoundedCornersEffect : public juce::ImageEffectFilter
{
public:

RoundedCornersEffect();

explicit RoundedCornersEffect
(
    const float& radius,
    const bool& roundTopLeftCorner = true,
    const bool& roundTopRightCorner = true,
    const bool& roundBottomLeftCorner = true,
    const bool& roundBottomRightCorner = true
);

~RoundedCornersEffect() override;

void setCornerRadius
(
    const float& radius,
    const bool& roundTopLeftCorner = true,
    const bool& roundTopRightCorner = true,
    const bool& roundBottomLeftCorner = true,
    const bool& roundBottomRightCorner = true
);

void setClipReductionPath (const juce::Path& clipPath);

void applyEffect
(
    juce::Image& sourceImage,
    juce::Graphics& destContext,
    float scaleFactor,
    float alpha
) override;

private:

float cornerRadius;

bool topLeftCorner;
bool topRightCorner;
bool bottomLeftCorner;
bool bottomRightCorner;

juce::Path clipReductionPath;
juce::Path scaledClipReductionPath;

bool isClipReductionPathProvided;

//==============================================================================

JUCE_LEAK_DETECTOR (RoundedCornersEffect)

};

#include “RoundedCornersEffect.h”

RoundedCornersEffect::RoundedCornersEffect() :
    cornerRadius (5.f),
    topLeftCorner (true),
    topRightCorner (true),
    bottomLeftCorner (true),
    bottomRightCorner (true),
    isClipReductionPathProvided (false)
{
    // default no arg ctor
}

RoundedCornersEffect::RoundedCornersEffect
(
    const float& radius,
    const bool& roundTopLeftCorner,
    const bool& roundTopRightCorner,
    const bool& roundBottomLeftCorner,
    const bool& roundBottomRightCorner
) :
    cornerRadius (radius),
    topLeftCorner (roundTopLeftCorner),
    topRightCorner (roundTopRightCorner),
    bottomLeftCorner (roundBottomLeftCorner),
    bottomRightCorner (roundBottomRightCorner),
    isClipReductionPathProvided (false)
{
    // ctor with args
}

RoundedCornersEffect::~RoundedCornersEffect() = default;

void RoundedCornersEffect::setCornerRadius
(
    const float& radius,
    const bool& roundTopLeftCorner,
    const bool& roundTopRightCorner,
    const bool& roundBottomLeftCorner,
    const bool& roundBottomRightCorner
)
{
    cornerRadius = radius;
    topLeftCorner = roundTopLeftCorner;
    topRightCorner = roundTopRightCorner;
    bottomLeftCorner = roundBottomLeftCorner;
    bottomRightCorner = roundBottomRightCorner;
}

void RoundedCornersEffect::setClipReductionPath (const juce::Path& clipPath)
{
    clipReductionPath = clipPath;
    scaledClipReductionPath = clipPath;
    isClipReductionPathProvided = true;
}

void RoundedCornersEffect::applyEffect (juce::Image& image, juce::Graphics& g, float scaleFactor, float alpha)
{
    if (isClipReductionPathProvided)
    {
        scaledClipReductionPath.clear();
        scaledClipReductionPath = clipReductionPath;
        scaledClipReductionPath.applyTransform (juce::AffineTransform::scale (scaleFactor));

        g.reduceClipRegion (scaledClipReductionPath, juce::AffineTransform::scale (scaleFactor));
    }

    if (!isClipReductionPathProvided && cornerRadius > 0.f)
    {
        clipReductionPath.clear();
        clipReductionPath.addRoundedRectangle
        (
            image.getBounds().toFloat().getX(),
            image.getBounds().toFloat().getY(),
            image.getBounds().toFloat().getWidth(),
            image.getBounds().toFloat().getHeight(),
            cornerRadius,
            cornerRadius,
            topLeftCorner,
            topRightCorner,
            bottomLeftCorner,
            bottomRightCorner
        );

        scaledClipReductionPath = clipReductionPath;

        g.reduceClipRegion (scaledClipReductionPath);
    }

    g.setOpacity (alpha);
    g.drawImageAt (image, 0, 0);
}

Hope this is useful, I found several related posts and just wanted to provide forum searchers a working reusable solution to adding rounded corners to a Component.

it should be noted that in case any mouse events are implemented on rounded corners component the hitTest function of the component should probably also work with those bounds

This is great! just wanted to post to say thanks, this does exactly what I was looking for and worked perfectly.

1 Like