I'm trying to develop a custom QProgressBar that will look like the following image :
I created a class that extends QProgressBar and implemented the paintEvent() :
void CircularProgressBar::paintEvent(QPaintEvent*) {
int progress = this->value();
int progressInDegrees = (double)(progress*360)/100;
int barWidth = 20;
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(QPen(Qt::black, barWidth, Qt::SolidLine,Qt::RoundCap));
painter.drawArc(barWidth/2, barWidth/2, this->width() - barWidth, this->height() - barWidth,
90*16, progressInDegrees*-16);}
This works great to draw the circular progress bar, but I'm having trouble with the linear gradient color of the bar. I tried creating a QPen with a QLinearGradient object and I tried setting the QPainter brush to a QLinearGradient object, but neither strategy worked. Is it possible to draw an arc with QPainter that has a linear gradient color?
I know this is an old question but I came across it some days ago and I think I have a solution. What you want is to create a conical gradient and clip the disk you want to use as circular loading bar. Here is an example:
widget.h:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class QPaintEvent;
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
void setLoadingAngle(int loadingAngle);
int loadingAngle() const;
void setDiscWidth(int width);
int discWidth() const;
protected:
void paintEvent(QPaintEvent *);
private:
int m_loadingAngle;
int m_width;
};
#endif // WIDGET_H
widget.cpp:
#include "widget.h"
#include <QPaintEvent>
#include <QPainter>
#include <QConicalGradient>
#include <QPen>
Widget::Widget(QWidget *parent) :
QWidget(parent),
m_loadingAngle(0),
m_width(0)
{
}
Widget::~Widget()
{
}
void Widget::setLoadingAngle(int loadingAngle)
{
m_loadingAngle = loadingAngle;
}
int Widget::loadingAngle() const
{
return m_loadingAngle;
}
void Widget::setDiscWidth(int width)
{
m_width = width;
}
int Widget::discWidth() const
{
return m_width;
}
void Widget::paintEvent(QPaintEvent *)
{
QRect drawingRect;
drawingRect.setX(rect().x() + m_width);
drawingRect.setY(rect().y() + m_width);
drawingRect.setWidth(rect().width() - m_width * 2);
drawingRect.setHeight(rect().height() - m_width * 2);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QConicalGradient gradient;
gradient.setCenter(drawingRect.center());
gradient.setAngle(90);
gradient.setColorAt(0, QColor(178, 255, 246));
gradient.setColorAt(1, QColor(5, 44, 50));
int arcLengthApproximation = m_width + m_width / 3;
QPen pen(QBrush(gradient), m_width);
pen.setCapStyle(Qt::RoundCap);
painter.setPen(pen);
painter.drawArc(drawingRect, 90 * 16 - arcLengthApproximation, -m_loadingAngle * 16);
}
main.cpp:
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.setDiscWidth(20);
w.setLoadingAngle(270);
w.show();
return a.exec();
}
And the result is:
Of course, it is not the complete and exact solution but I think it is everything you need to know in order to achieve what you want. The rest are details not hard to implement.
This solution is not exactly what you're after; the gradient goes from top to bottom, rather than around the circle:
#include <QtWidgets>
class Widget : public QWidget
{
public:
Widget() {
resize(200, 200);
}
void paintEvent(QPaintEvent *) {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
const QRectF bounds(0, 0, width(), height());
painter.fillRect(bounds, "#1c1c1c");
QPen pen;
pen.setCapStyle(Qt::RoundCap);
pen.setWidth(20);
QLinearGradient gradient;
gradient.setStart(bounds.width() / 2, 0);
gradient.setFinalStop(bounds.width() / 2, bounds.height());
gradient.setColorAt(0, "#1c1c1c");
gradient.setColorAt(1, "#28ecd6");
QBrush brush(gradient);
pen.setBrush(brush);
painter.setPen(pen);
QRectF rect = QRectF(pen.widthF() / 2.0, pen.widthF() / 2.0, width() - pen.widthF(), height() - pen.widthF());
painter.drawArc(rect, 90 * 16, 0.65 * -360 * 16);
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Widget w;
w.show();
return app.exec();
}
However, it is an arc with a linear gradient! :p
Related
I'm developing with Qt-5.15.1. I want to customize QGraphicsItem, and in this customized item one rectangle and some surrounding circles added. The little circles will show when the mouse is hovering on that rectangle. So I reimplement the hoverEnterEvent and hoverLeaveEvent function to receive mouse hover event, please refer to minimal example.
Then, in paint event I can determine whether draw circles or not based on _mouseEnter.
Here comes the problem, I found the hoverEnterEvent will triggered as soon as mouse enter the border of that rectangle, however quickly hoverLeaveEvent is also triggered as mouse go through the border, being near the center of rectangle. Seems the border is the entity of mouse hover event, not the filled rectangle. So I can only show circles on when mouse is hovering on the border of that rectangle.
I don't know if I miss something? In my opinion, shape() and boundingRect() will affect these mouse events on when event happens? I want to make shape() to return a filled rectangle of QPainterPath, but don't know how to.
Update: minimal example
customizeitem.cpp
#include "customizeitem.h"
#include <QDebug>
CustomizeItem::CustomizeItem(QGraphicsItem *parent):
QGraphicsItem(parent),
_bbox(0,0,120, 120),_radius(7),
_mouseEnter(false)
{
setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);
setAcceptHoverEvents(true);
_rectRect = QRect(QPoint(_radius*4, _radius*4), QPoint(_bbox.width() - _radius*4, _bbox.height() - _radius*4));
QPointF upCenter(_bbox.width()/2, _radius);
QPointF rCenter(_bbox.width() - _radius, _bbox.height() / 2);
QPointF downCenter(_bbox.width()/2, _bbox.height() - _radius);
QPoint lCenter(_radius, _bbox.height() / 2);
_anchorRects.push_back(QRectF(upCenter, QSizeF(_radius, _radius)));
_anchorRects.push_back(QRectF(rCenter, QSizeF(_radius, _radius)));
_anchorRects.push_back(QRectF(downCenter, QSizeF(_radius, _radius)));
_anchorRects.push_back(QRectF(lCenter, QSizeF(_radius, _radius)));
}
void CustomizeItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
qInfo() << "mouse enter";
_mouseEnter = true;
update();
QGraphicsItem::hoverEnterEvent(event);
}
void CustomizeItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
qInfo() << "mouse leave";
_mouseEnter = false;
update();
QGraphicsItem::hoverLeaveEvent(event);
}
QRectF CustomizeItem::boundingRect() const
{
return shape().boundingRect();
}
void CustomizeItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
painter->setRenderHint(QPainter::Antialiasing);
drawAnchors(painter);
painter->fillRect(_rectRect, QColor(255, 0, 0));
}
QPainterPath CustomizeItem::shape() const
{
QPainterPath path;
path.moveTo(_bbox.topLeft());
path.addRect(_bbox);
QPainterPathStroker stroker;
stroker.setWidth(10);
return stroker.createStroke(path);
}
void CustomizeItem::drawAnchors(QPainter *painter)
{
if(_mouseEnter)
{
for(int i = 0; i < _anchorRects.size(); i++)
{
QPainterPath path;
path.moveTo(_anchorRects[0].center());
path.addEllipse(_anchorRects[i].center(), _radius, _radius);
painter->drawPath(path);
}
}
}
customizeitem.h
#ifndef CUSTOMIZEITEM_H
#define CUSTOMIZEITEM_H
#include <QGraphicsItem>
#include <QObject>
#include <QPainter>
class CustomizeItem : public QObject, public QGraphicsItem
{
Q_OBJECT
public:
enum { Type = UserType + 1 };
explicit CustomizeItem(QGraphicsItem *parent = nullptr);
~CustomizeItem() = default;
protected:
void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *);
QPainterPath shape() const;
private:
void drawAnchors(QPainter *painter);
QRect _bbox;
float _radius; // radius for circle anchor
QVector<QRectF> _anchorRects;
QRect _rectRect;
bool _mouseEnter;
};
#endif // CUSTOMIZEITEM_H
maiwindow.cpp
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "customizeitem.h"
#include <QGraphicsView>
#include <QVBoxLayout>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QVBoxLayout *layout = new QVBoxLayout(centralWidget());
layout->setContentsMargins(0,0,0,0);
QGraphicsView *view = new QGraphicsView(this);
view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QGraphicsScene *scene = new QGraphicsScene;
CustomizeItem *item = new CustomizeItem;
scene->addItem(item);
view->setScene(scene);
layout->addWidget(view);
}
MainWindow::~MainWindow()
{
delete ui;
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
QPainterPathStroker generates a hollow region around the rectangle so as soon as that border passes it is already out of the shape. The solution is to join that edge with the inner region:
QPainterPath CustomizeItem::shape() const
{
QPainterPath path;
path.moveTo(_bbox.topLeft());
path.addRect(_bbox);
QPainterPathStroker stroker;
stroker.setWidth(10);
QPainterPath strokerPath = stroker.createStroke(path);
strokerPath.addPath(path); // join
return strokerPath;
}
Or just adjust the bounding box rectangle to accommodate the extra size.
QPainterPath CustomizeItem::shape() const
{
QPainterPath path;
path.addRect(_bbox.adjusted(-5, -5, 5, 5));
return path;
}
Since your "hit" shape is rectangular, you can probably just re-implement boundingRect(), since the default QGraphicsItem::shape() actually uses boundingRect() result (according to docs).
QRectF CustomizeItem::boundingRect() const
{
return QRectF(_bbox.adjusted(-5, -5, 5, 5));
}
I have tried this method (Answer of NikitaFeodonit) for offscreen rendering and creating a QImage and saving to disk. Everything is working fine, except when using a QColor with Alpha-Channel for QPen.
QPainter.drawEllipse without ALPHA in Pen-Color
QPainter.drawEllipse with ALPHA in Pen-Color
Here is my source:
Using OpenGlOffscreenSurface from this method:
Header ExamplePaintSurface.h:
#ifndef EXAMPLEPAINTSURFACE_H
#define EXAMPLEPAINTSURFACE_H
#include <QPainter>
#include "OpenGlOffscreenSurface.h"
class ExamplePaintSurface
: public OpenGlOffscreenSurface
{
public:
explicit ExamplePaintSurface(
QScreen* targetScreen = nullptr,
const QSize& size = QSize (1, 1));
void renderImage(bool useAlpha);
virtual ~ExamplePaintSurface() override;
protected:
virtual void initializeGL() override;
virtual void resizeGL(
int width,
int height) override;
virtual void paintGL() override;
private:
bool m_useAlpha;
};
#endif // EXAMPLEPAINTSURFACE_H
Source ExamplePaintSurface.cpp:
#include "ExamplePaintSurface.h"
ExamplePaintSurface::ExamplePaintSurface(
QScreen* targetScreen,
const QSize& size)
: OpenGlOffscreenSurface(targetScreen, size) {}
void ExamplePaintSurface::renderImage(bool useAlpha)
{
m_useAlpha = useAlpha;
this->paintGL();
}
ExamplePaintSurface::~ExamplePaintSurface() {}
void ExamplePaintSurface::initializeGL() {}
void ExamplePaintSurface::resizeGL(int width, int height) {}
void ExamplePaintSurface::paintGL()
{
QPainter painter(getPaintDevice());
painter.eraseRect(0,0,200,200);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
painter.setCompositionMode (QPainter::CompositionMode_Source);
painter.fillRect(0,0,200,200, Qt::transparent);
painter.setCompositionMode (QPainter::CompositionMode_SourceOver);
QPen pen;
if(m_useAlpha == true)
pen.setColor(QColor(255, 0, 0, 150));
else
pen.setColor(QColor(255, 0, 0, 255));
QBrush brush(Qt::yellow);
brush.setStyle(Qt::SolidPattern);
pen.setWidth(8);
painter.setPen(pen);
painter.setBrush(brush);
painter.drawEllipse(50,50,100,100);
//painter.drawText(20, 40, "Test"); // <-- drawing here
painter.end();
}
Source main.cpp:
#include <QApplication>
#include "ExamplePaintSurface.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ExamplePaintSurface paintSurface;
paintSurface.resize(200, 200);
paintSurface.renderImage(false);
paintSurface.render();
QImage image = paintSurface.grabFramebuffer();
image.save("Ellipse_noAlpha.png");
paintSurface.renderImage(true);
paintSurface.render();
image = paintSurface.grabFramebuffer();
image.save("Ellipse_alpha.png");
return a.exec();
}
I am trying to draw a transparent image using QImage but everytime it gives black background. I have a image background,on that I want to draw a circle which should be trasparent(with no background).How can I do that?
I have used this code
QImage image(size, QImage::Format_ARGB32);
image.fill(qRgba(0,0,0,0));
// Pick an arbitrary size for the circle
const int centerX = size.width() / 2;
const int centerY = size.height() / 2;
const int radius = std::min(centerX, centerY) * 2 / 3;
const int diameter = radius * 2;
// Draw the circle!
QPainter painter(&image);
painter.setPen(Qt::yellow);
painter.drawEllipse(centerX-radius, centerY-radius, diameter, diameter);
http://qt-project.org/doc/qt-4.8/qpainter.html#settings
http://qt-project.org/doc/qt-4.8/qpainter.html#setBrush
The painter's brush defines how shapes are filled.
Hope that helps.
EDIT: Added an awesome example:
Basically what happens below, is the window is set to have a background color (so that the alpha value of the QImage is noticeable and predicable). The QImage is initialized to have a color with an alpha value less than 255. The image gets painted when the widget updates (when shown in the main).
widget.cpp
#include "widget.h"
#include <QImage>
#include <QPainter>
#include <QPalette>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
init_image();
QPalette p = this->palette();
p.setColor(QPalette::Background, Qt::white);
this->setPalette(p);
}
void Widget::init_image()
{
image = new QImage(200, 200, QImage::Format_ARGB32);
int opacity = 50;// Set this between 0 and 255
image->fill(QColor(0,0,0,opacity));
QPainter painter (image);
painter.setPen(Qt::green);
painter.drawEllipse(10, 10, 100, 100);
}
Widget::~Widget()
{
}
void Widget::paintEvent(QPaintEvent * e)
{
QPainter painter(this);
painter.drawImage(0,0, *image,0,0,-1,-1,Qt::AutoColor);
}
Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QPaintEvent>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
void init_image();
public slots:
void paintEvent(QPaintEvent *);
private:
QImage * image;
};
#endif // WIDGET_H
Main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
For example, if I wanted to display the player's inventory in a game using a QGraphicsView, how could I enforce a grid-based view where dragging and dropping items always results in them being aligned to a grid?
You can handle the appropriate mouse events in a QGraphicsScene subclass:
#include <QApplication>
#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <QMainWindow>
#include <math.h>
class GridScene : public QGraphicsScene
{
public:
GridScene() :
mCellSize(25, 25)
{
}
protected:
// Efficiently draws a grid in the background.
// For more information: http://www.qtcentre.org/threads/5609-Drawing-grids-efficiently-in-QGraphicsScene?p=28905#post28905
void drawBackground(QPainter *painter, const QRectF &rect)
{
qreal left = int(rect.left()) - (int(rect.left()) % mCellSize.width());
qreal top = int(rect.top()) - (int(rect.top()) % mCellSize.height());
QVarLengthArray<QLineF, 100> lines;
for (qreal x = left; x < rect.right(); x += mCellSize.width())
lines.append(QLineF(x, rect.top(), x, rect.bottom()));
for (qreal y = top; y < rect.bottom(); y += mCellSize.height())
lines.append(QLineF(rect.left(), y, rect.right(), y));
painter->drawLines(lines.data(), lines.size());
}
void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
mDragged = qgraphicsitem_cast<QGraphicsItem*>(itemAt(mouseEvent->scenePos(), QTransform()));
if (mDragged) {
mDragOffset = mouseEvent->scenePos() - mDragged->pos();
} else
QGraphicsScene::mousePressEvent(mouseEvent);
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
if (mDragged) {
// Ensure that the item's offset from the mouse cursor stays the same.
mDragged->setPos(mouseEvent->scenePos() - mDragOffset);
} else
QGraphicsScene::mouseMoveEvent(mouseEvent);
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
if (mDragged) {
int x = floor(mouseEvent->scenePos().x() / mCellSize.width()) * mCellSize.width();
int y = floor(mouseEvent->scenePos().y() / mCellSize.height()) * mCellSize.height();
mDragged->setPos(x, y);
mDragged = 0;
} else
QGraphicsScene::mouseReleaseEvent(mouseEvent);
}
private:
// The size of the cells in the grid.
const QSize mCellSize;
// The item being dragged.
QGraphicsItem *mDragged;
// The distance from the top left of the item to the mouse position.
QPointF mDragOffset;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QMainWindow w;
w.resize(400, 400);
w.show();
QGraphicsView view(&w);
view.setScene(new GridScene());
view.resize(w.size());
view.setSceneRect(0, 0, view.size().width(), view.size().height());
view.show();
view.scene()->addRect(0, 0, 25, 25)->setBrush(QBrush(Qt::blue));
return a.exec();
}
#include "main.moc"
In the code below, I have three QGraphicsItems that are laid out by QGraphicsLinearLayout, which is set as layout to a QGraphicsWidget.
#include <QApplication>
#include <QBrush>
#include <QDebug>
#include <QGraphicsItem>
#include <QGraphicsLayoutItem>
#include <QGraphicsLinearLayout>
#include <QGraphicsRectItem>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsWidget>
#include <QPen>
class MyShape : public QGraphicsRectItem, public QGraphicsLayoutItem {
public:
MyShape(void) {
setPen(QPen(QBrush(Qt::black), 1));
setBrush(QBrush(Qt::green));
setRect(0, 0, 20, 20);
}
virtual QSizeF sizeHint(Qt::SizeHint which,
const QSizeF& constraint = QSizeF()) const {
Q_UNUSED(which);
Q_UNUSED(constraint);
return boundingRect().size();
}
virtual void setGeometry(const QRectF& rect) {
setPos(rect.topLeft());
}
};
int main(int argc, char** argv) {
QApplication app(argc, argv);
QGraphicsScene scene;
MyShape* shape1 = new MyShape;
MyShape* shape2 = new MyShape;
MyShape* shape3 = new MyShape;
scene.addItem(shape1);
scene.addItem(shape2);
scene.addItem(shape3);
QGraphicsLinearLayout* layout = new QGraphicsLinearLayout;
layout->addItem(shape1);
layout->addItem(shape2);
layout->addItem(shape3);
QGraphicsWidget* container = new QGraphicsWidget;
container->setLayout(layout);
scene.addItem(container);
container->setPos(300, 300); // This doesn't appear to have any affect
// Item for indicating origin
QGraphicsRectItem* tmp = scene.addRect(0, 0, 2, 2, QPen(),
QBrush(Qt::green));
tmp->setPos(0, 0);
qDebug() << tmp->scenePos();
qDebug() << container->scenePos();
QGraphicsView view;
view.setScene(&scene);
view.centerOn(0, 0);
view.show();
return app.exec();
}
Then, I try to move the QGraphicsWidget within the scene by calling setPos(), but it doesn't appear to work (the QGraphicsItems remain in the same place). However, something appears to happen since the scrollbars change. It seems that the QGraphicsWidget moves, without taking the QGraphicsItems along with it.
Why?
EDIT:
By suggestion from DerManu, I changed the inheritance of MyShape to QGraphicsWidget, and with the following code, it works:
#include <QApplication>
#include <QBrush>
#include <QDebug>
#include <QGraphicsItem>
#include <QGraphicsLayoutItem>
#include <QGraphicsLinearLayout>
#include <QGraphicsRectItem>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsWidget>
#include <QPen>
class MyShape : public QGraphicsWidget {
public:
MyShape(void) {
setMinimumSize(20, 20);
setMaximumSize(20, 20);
setPreferredSize(20, 20);
}
QRectF boundingRect(void) const {
QRectF box(0, 0, 22, 22);
box.translate(box.center() * -1);
return box;
}
void paint(QPainter* painter,
const QStyleOptionGraphicsItem* option,
QWidget* widget) {
Q_UNUSED(option);
Q_UNUSED(widget);
// Set box border appearance
painter->setPen(QPen(Qt::black, 1));
// Set box background appearance
painter->setBrush(QBrush(Qt::green));
QRectF box(0, 0, 20, 20);
box.translate(box.center() * -1);
painter->drawRect(box);
}
};
int main(int argc, char** argv) {
QApplication app(argc, argv);
QGraphicsScene scene;
MyShape* shape1 = new MyShape;
MyShape* shape2 = new MyShape;
MyShape* shape3 = new MyShape;
scene.addItem(shape1);
scene.addItem(shape2);
scene.addItem(shape3);
QGraphicsLinearLayout* layout = new QGraphicsLinearLayout(Qt::Vertical);
layout->addItem(shape1);
layout->addItem(shape2);
layout->addItem(shape3);
QGraphicsWidget* container = new QGraphicsWidget;
container->setLayout(layout);
scene.addItem(container);
container->setPos(200, 200); // This doesn't appear to have any affect
scene.setSceneRect(-300, -300, 600, 600);
// Item for indicating origin
QGraphicsRectItem* tmp = scene.addRect(0, 0, 2, 2, QPen(),
QBrush(Qt::green));
tmp->setPos(0, 0);
qDebug() << tmp->scenePos();
qDebug() << container->scenePos();
QGraphicsView view;
view.setScene(&scene);
view.centerOn(0, 0);
view.show();
return app.exec();
}
So what is it that QGraphicsWidget provides that I missed in the original code such that using setPos on the container item correctly moves the position of the MyShape items?