Skip to content

Instantly share code, notes, and snippets.

@oclero
Last active September 2, 2021 12:31
Show Gist options
  • Save oclero/a3b57f82d422365e6bc3295e5e473999 to your computer and use it in GitHub Desktop.
Save oclero/a3b57f82d422365e6bc3295e5e473999 to your computer and use it in GitHub Desktop.
C++ RoundedRectangle for QML usage

How to draw a C++ QtQuick rectangle with different border radius

Theses classes show how to make a QQuickItem that draws a rectangle with different border radiuses /different roundings.

  • RoundedRectangle: the C++ class that draws the rectangle, using QPainter API.
  • RoundedCorners: a C++ class to simplify the definition of different roundings.
RoundedRectangle {
  width: 100
  height: 100
  corners: RoundedCorners {
    topLeft: 4
    bottomLeft: 8
    topRight: 0
    bottomRight: 3
  }
}
#include "RoundedCorners.h"
RoundedCorners::RoundedCorners(QObject* parent) : QObject(parent) {}
RoundedCorners::RoundedCorners(qreal topLeft, qreal bottomLeft, qreal bottomRight, qreal topRight, QObject* parent)
: QObject(parent), _topLeft(topLeft), _bottomLeft(bottomLeft), _bottomRight(bottomRight), _topRight(topRight) {
}
qreal RoundedCorners::topLeft() const { return _topLeft; }
qreal RoundedCorners::bottomLeft() const { return _bottomLeft; }
qreal RoundedCorners::bottomRight() const { return _bottomRight; }
qreal RoundedCorners::topRight() const { return _topRight; }
bool RoundedCorners::all() const {
return _topLeft != 0 && _topRight != 0 && _bottomRight != 0 && _bottomLeft != 0;
}
bool RoundedCorners::none() const {
return _topLeft == 0 && _topRight == 0 && _bottomRight == 0 && _bottomLeft == 0;
}
void RoundedCorners::setTopLeft(qreal value) {
value = std::max(0., value);
if (value != _topLeft) {
_topLeft = value;
emit topLeftChanged();
}
}
void RoundedCorners::setBottomLeft(qreal value) {
value = std::max(0., value);
if (value != _bottomLeft) {
_bottomLeft = value;
emit bottomLeftChanged();
}
}
void RoundedCorners::setBottomRight(qreal value) {
value = std::max(0., value);
if (value != _bottomRight) {
_bottomRight = value;
emit bottomRightChanged();
}
}
void RoundedCorners::setTopRight(qreal value) {
value = std::max(0., value);
if (value != _topRight) {
_topRight = value;
emit topRightChanged();
}
}
#pragma once
#include <QObject>
#include <qqml.h>
/**
* @brief Specifies the radius for a rectangle's rounded corners.
*/
class RoundedCorners : public QObject {
Q_OBJECT
Q_PROPERTY(qreal topLeft READ topLeft WRITE setTopLeft NOTIFY topLeftChanged)
Q_PROPERTY(qreal bottomLeft READ bottomLeft WRITE setBottomLeft NOTIFY bottomLeftChanged)
Q_PROPERTY(qreal bottomRight READ bottomRight WRITE setBottomRight NOTIFY bottomRightChanged)
Q_PROPERTY(qreal topRight READ topRight WRITE setTopRight NOTIFY topRightChanged)
QML_ELEMENT
public:
RoundedCorners(QObject* parent = nullptr);
RoundedCorners(qreal topLeft, qreal bottomLeft, qreal bottomRight, qreal topRight, QObject* parent = nullptr);
qreal topLeft() const;
qreal bottomLeft() const;
qreal bottomRight() const;
qreal topRight() const;
bool all() const;
bool none() const;
public slots:
void setTopLeft(qreal value);
void setBottomLeft(qreal value);
void setBottomRight(qreal value);
void setTopRight(qreal value);
signals:
void topLeftChanged();
void bottomLeftChanged();
void bottomRightChanged();
void topRightChanged();
private:
qreal _topLeft{0};
qreal _bottomLeft{0};
qreal _bottomRight{0};
qreal _topRight{0};
};
#include "RoundedRectangle.h"
#include <QPainter>
#include <QPainterPath>
RoundedRectangle::RoundedRectangle(QQuickItem* parent) : QQuickPaintedItem(parent) {
_borderPen = QPen(Qt::black, 1., Qt::PenStyle::SolidLine, Qt::PenCapStyle::SquareCap, Qt::PenJoinStyle::MiterJoin);
// Enable antialiasing by default, as on QML's Rectangle.
setAntialiasing(true);
// Forward signals and update accordingly.
QObject::connect(&_corners, &RoundedCorners::topLeftChanged, this, [this](){
emit radiusTopLeftChanged();
update();
});
QObject::connect(&_corners, &RoundedCorners::bottomLeftChanged, this, [this](){
emit radiusBottomLeftChanged();
update();
});
QObject::connect(&_corners, &RoundedCorners::bottomRightChanged, this, [this](){
emit radiusBottomRightChanged();
update();
});
QObject::connect(&_corners, &RoundedCorners::topRightChanged, this, [this](){
emit radiusTopRightChanged();
update();
});
}
void RoundedRectangle::paint(QPainter *painter) {
if (antialiasing()) {
painter->setRenderHint(QPainter::RenderHint::Antialiasing);
}
const QSizeF itemSize = size();
const auto borderWidth = _borderPen.widthF();
if (borderWidth > 0.) {
const auto halfBorderWidth = borderWidth / 2;
const auto borderCorners = RoundedCorners {
std::max(0., _corners.topLeft() - halfBorderWidth),
std::max(0., _corners.bottomLeft() - halfBorderWidth),
std::max(0., _corners.bottomRight() - halfBorderWidth),
std::max(0., _corners.topRight() - halfBorderWidth),
};
// Fill.
if (_color.alpha() > 0) {
painter->setBrush(_color);
painter->setPen(Qt::PenStyle::NoPen);
if (_borderPen.color().alpha() == 255) {
drawRect(*painter,
halfBorderWidth, halfBorderWidth,
itemSize.width() - borderWidth,
itemSize.height() - borderWidth,
borderCorners);
} else {
drawRect(*painter, 0., 0., itemSize.width(), itemSize.height(), _corners);
}
}
// Border.
if (_borderPen.color().alpha() > 0) {
painter->setBrush(Qt::BrushStyle::NoBrush);
painter->setPen(_borderPen);
drawRect(*painter,
halfBorderWidth, halfBorderWidth,
itemSize.width() - borderWidth ,
itemSize.height() - borderWidth,
borderCorners);
}
} else if (_color.alpha() > 0) {
painter->setBrush(_color);
painter->setPen(Qt::PenStyle::NoPen);
drawRect(*painter, 0., 0., itemSize.width(), itemSize.height(), _corners);
}
}
void RoundedRectangle::drawRect(QPainter &painter, const qreal x, const qreal y,
const qreal w, const qreal h, const RoundedCorners &corners) {
QPainterPath path;
const auto minSide = std::min(w, h) / 2;
qreal radius;
qreal diameter;
// To-left corner.
if (corners.topLeft() > 0) {
radius = std::min(minSide, corners.topLeft());
diameter = 2 * radius;
path.moveTo(x + diameter, y);
path.arcTo(x, y, diameter, diameter, 90, 90);
} else {
path.moveTo(x, y);
}
// Left side & Bottom-left corner.
if (corners.bottomLeft() > 0) {
radius = std::min(minSide, corners.bottomLeft());
diameter = 2 * radius;
path.arcTo(x, y + h - diameter, diameter, diameter, 180, 90);
} else {
path.lineTo(x, y + h);
}
// Bottom side & Bottom-right corner.
if (corners.bottomRight() > 0) {
radius = std::min(minSide, corners.bottomRight());
diameter = 2 * radius;
path.arcTo(x + w - diameter, y + h - diameter, diameter, diameter, 270, 90);
} else {
path.lineTo(x + w, y + h);
}
// Right side & Top-right corner.
if (corners.topRight()) {
radius = std::min(minSide, corners.topRight());
diameter = 2 * radius;
path.arcTo(x + w - diameter, y, diameter, diameter, 360, 90);
} else {
path.lineTo(x + w, y);
}
path.closeSubpath();
painter.drawPath(path);
}
const QColor &RoundedRectangle::color() const {
return _color;
}
void RoundedRectangle::setColor(const QColor &value) {
if (value != _color) {
_color = value;
emit colorChanged();
update();
}
}
QColor RoundedRectangle::borderColor() const {
return _borderPen.color();
}
void RoundedRectangle::setBorderColor(const QColor &value) {
if (value != _borderPen.color()) {
_borderPen.setColor(value);
emit borderColorChanged();
update();
}
}
qreal RoundedRectangle::borderWidth() const {
return _borderPen.widthF();
}
void RoundedRectangle::setBorderWidth(qreal value) {
value = std::max(0., value);
if (value != _borderPen.widthF()) {
_borderPen.setWidthF(value);
emit borderWidthChanged();
update();
}
}
qreal RoundedRectangle::radiusTopLeft() const {
return _corners.topLeft();
}
void RoundedRectangle::setRadiusTopLeft(qreal value) {
_corners.setTopLeft(value);
}
qreal RoundedRectangle::radiusBottomLeft() const {
return _corners.bottomLeft();
}
void RoundedRectangle::setRadiusBottomLeft(qreal value) {
_corners.setBottomLeft(value);
}
qreal RoundedRectangle::radiusBottomRight() const {
return _corners.bottomRight();
}
void RoundedRectangle::setRadiusBottomRight(qreal value) {
_corners.setBottomRight(value);
}
qreal RoundedRectangle::radiusTopRight() const {
return _corners.topRight();
}
void RoundedRectangle::setRadiusTopRight(qreal value) {
_corners.setTopRight(value);
}
#pragma once
#include <QColor>
#include <QPen>
#include <QQuickPaintedItem>
#include "RoundedCorners.h"
/**
* @brief Draws a rectangle with (possibly different) rounded corners.
*/
class RoundedRectangle : public QQuickPaintedItem {
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor NOTIFY borderColorChanged)
Q_PROPERTY(qreal borderWidth READ borderWidth WRITE setBorderWidth NOTIFY borderWidthChanged)
Q_PROPERTY(qreal radiusTopLeft READ radiusTopLeft WRITE setRadiusTopLeft NOTIFY radiusTopLeftChanged)
Q_PROPERTY(qreal radiusBottomLeft READ radiusBottomLeft WRITE setRadiusBottomLeft NOTIFY radiusBottomLeftChanged)
Q_PROPERTY(qreal radiusBottomRight READ radiusBottomRight WRITE setRadiusBottomRight NOTIFY radiusBottomRightChanged)
Q_PROPERTY(qreal radiusTopRight READ radiusTopRight WRITE setRadiusTopRight NOTIFY radiusTopRightChanged)
QML_ELEMENT
public:
RoundedRectangle(QQuickItem* parent = nullptr);
virtual void paint(QPainter *painter) override;
const QColor &color() const;
QColor borderColor() const;
qreal borderWidth() const;
qreal radiusTopLeft() const;
qreal radiusBottomLeft() const;
qreal radiusBottomRight() const;
qreal radiusTopRight() const;
public slots:
void setColor(const QColor &value);
void setBorderColor(const QColor &value);
void setBorderWidth(qreal value);
void setRadiusTopLeft(qreal value);
void setRadiusBottomLeft(qreal value);
void setRadiusBottomRight(qreal value);
void setRadiusTopRight(qreal value);
private:
static void drawRect(QPainter &painter, const qreal x, const qreal y,
const qreal w, const qreal h, const RoundedCorners &corners);
signals:
void colorChanged();
void borderColorChanged();
void borderWidthChanged();
void radiusTopLeftChanged();
void radiusBottomLeftChanged();
void radiusBottomRightChanged();
void radiusTopRightChanged();
private:
RoundedCorners _corners;
QColor _color;
QPen _borderPen;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment